c++学习(多线程)[33]

news2024/10/6 17:22:44

thread

本质封装操作系统的库
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
事实证明,两个线程在cpu中交错运行

thread传参为模板参数,应用折叠,都会变成左值,所以count还是0
在这里插入图片描述
在这里插入图片描述

sleep_until

在C++中,没有直接的sleep_until函数,但可以使用std::this_thread::sleep_until来实现类似的功能。std::this_thread::sleep_until函数可以让当前线程休眠,直到指定的时间点。

下面是一个示例代码,演示了如何使用std::this_thread::sleep_until函数来实现休眠直到指定的时间点:

#include <iostream>
#include <chrono>
#include <thread>

int main() {
    // 获取当前时间点
    std::chrono::system_clock::time_point now = std::chrono::system_clock::now();

    // 设置休眠时间为当前时间点加上5秒
    std::chrono::system_clock::time_point sleep_time = now + std::chrono::seconds(5);

    // 休眠直到指定的时间点
    std::this_thread::sleep_until(sleep_time);

    // 输出休眠结束后的时间点
    std::cout << "Finished sleeping at " << std::chrono::system_clock::now() << std::endl;

    return 0;
}

在上述代码中,我们首先使用std::chrono::system_clock::now()获取当前时间点。然后,我们设置休眠时间为当前时间点加上5秒,即now + std::chrono::seconds(5)。接下来,我们使用std::this_thread::sleep_until函数休眠直到指定的时间点。

运行上述代码,将会在休眠5秒后输出休眠结束后的时间点。

请注意,std::this_thread::sleep_until函数接受一个std::chrono::time_point类型的参数,表示要休眠直到的时间点。因此,可以使用std::chrono库中的各种时间表示类型来指定休眠的时间点,如std::chrono::system_clock::time_pointstd::chrono::steady_clock::time_point等。

sleep_for

相对时间休眠
std::this_thread::sleep_for是一个C++标准库中的函数,用于使当前线程休眠指定的时间段。

下面是一个示例代码,演示了如何使用std::this_thread::sleep_for函数来使当前线程休眠指定的时间段:

#include <iostream>
#include <chrono>
#include <thread>

int main() {
    // 休眠2秒
    std::chrono::seconds sleep_duration(2);
    std::this_thread::sleep_for(sleep_duration);

    std::cout << "Finished sleeping for 2 seconds" << std::endl;

    return 0;
}

在上述代码中,我们使用std::chrono::seconds类型创建了一个表示2秒的时间段对象sleep_duration。然后,我们使用std::this_thread::sleep_for函数将当前线程休眠2秒。

运行上述代码,将会在休眠2秒后输出"Finished sleeping for 2 seconds"。

请注意,std::this_thread::sleep_for函数接受一个std::chrono::duration类型的参数,表示要休眠的时间段。std::chrono::duration是一个模板类,可以用于表示各种时间单位的时间段,如秒、毫秒、微秒等。可以使用std::chrono库中的各种时间表示类型来创建时间段对象,如std::chrono::secondsstd::chrono::millisecondsstd::chrono::microseconds等。
在这里插入图片描述

get_id

可以使用std::this_thread::get_id函数来获取当前线程的唯一标识符
在这里插入图片描述

在这里插入图片描述

this_thread

std::this_thread是C++标准库中的一个命名空间,其中定义了一些与当前线程相关的函数和类型。

下面是std::this_thread命名空间中一些常用的函数和类型:

  • std::this_thread::get_id(): 返回当前线程的唯一标识符,类型为std::thread::id
  • std::this_thread::yield(): 提示线程调度器将当前线程放弃,以便其他线程有机会运行。
  • std::this_thread::sleep_for(): 使当前线程休眠指定的时间段。
  • std::this_thread::sleep_until(): 使当前线程休眠直到指定的时间点。
  • std::this_thread::hardware_concurrency(): 返回当前系统支持的并发线程数。

此外,std::this_thread命名空间还包含了一些与线程相关的类型,如std::thread::id表示线程的唯一标识符。

以下是一个示例代码,演示了如何使用std::this_thread命名空间中的一些函数:

#include <iostream>
#include <chrono>
#include <thread>

int main() {
    // 获取当前线程的标识符
    std::thread::id thread_id = std::this_thread::get_id();
    std::cout << "Thread ID: " << thread_id << std::endl;

    // 提示线程调度器将当前线程放弃
    std::this_thread::yield();

    // 使当前线程休眠1秒
    std::this_thread::sleep_for(std::chrono::seconds(1));

    // 获取当前系统支持的并发线程数
    unsigned int num_threads = std::this_thread::hardware_concurrency();
    std::cout << "Number of concurrent threads supported: " << num_threads << std::endl;

    return 0;
}

在上述代码中,我们使用std::this_thread::get_id函数获取当前线程的标识符,并使用std::cout输出。然后,我们使用std::this_thread::yield函数提示线程调度器将当前线程放弃。接下来,我们使用std::this_thread::sleep_for函数使当前线程休眠1秒。最后,我们使用std::this_thread::hardware_concurrency函数获取当前系统支持的并发线程数,并使用std::cout输出。

运行上述代码,将会输出当前线程的标识符、休眠1秒后的时间点以及当前系统支持的并发线程数。

mutex

互斥锁
不加锁会出现x的同时相加
加在循环内部 并行
锁中间比较怕抛异常会导致死锁
在这里插入图片描述
加在外部 串行
在这里插入图片描述
此例子理论,并行快,实际串行比较快在此程序中,上面慢在线程切换,申请锁和解锁花费了大量的时间,实际工作时间确非常少

解决死锁问题:try
在这里插入图片描述
解决死锁问题:RAII

#include <iostream>
#include <mutex>
#include <vector>
#include <atomic>
using namespace std;
//RAII
template<class Lock>
class LockGuard
{
public:
	LockGuard(Lock& lk)
		:_lock(lk)
	{
		cout << "thread:" << this_thread::get_id() << "加锁" << endl;
		_lock.lock();
	}
	~LockGuard()
	{
		cout << "thread:" << this_thread::get_id() << "解锁" << endl;
		_lock.unlock();
	}
private:
	Lock& _lock;
};


int main()
{
	mutex mtx;
	atomic<int> x = 0;
	int n = 100;
	int m;
	cin >> m;
	vector<thread> v(m);
	for (int i = 0; i < m; i++)
	{  //移动赋值给vector中的线程	
		v[i] = thread([&]() {
		for (int i = 0; i < n; ++i)
			{
				//mtx.lock();
				LockGuard<mutex> lk(mtx);
				//库里面的
				//lock_guard<mutex> lk(mtx);
				std::cout << this_thread::get_id() << ":" << i << endl;
				std::this_thread::sleep_for(std::chrono::milliseconds(100));
				++x;
				//mtx.unlock();
			}
			});
	}
	for (auto& t : v) 
	{
		t.join();
	}
	std::cout << x << endl;
	return 0;
}

try_lock

返回一个bool值,有人锁返回false

ref

强制让接收的地方变成左值引用
在这里插入图片描述

在这里插入图片描述
使用lambda可有同样效果

#include <iostream>
#include <mutex>
using namespace std;

int main()
{
	mutex mtx;
	int x = 0;
	int n = 10;
	thread t1([&]() {
		for (int i = 0; i < n; ++i)
		{
			mtx.lock();

			cout << this_thread::get_id() << ":" << i << endl;
			std::this_thread::sleep_for(std::chrono::milliseconds(100));
			++x;
			mtx.unlock();
		}
		});
	thread t2([&]() {
		for (int i = 0; i < n; ++i)
		{
			mtx.lock();

			cout << this_thread::get_id() << ":" << i << endl;
			std::this_thread::sleep_for(std::chrono::milliseconds(100));
			++x;
			mtx.unlock();
		}
		});

	t1.join();
	t2.join();
	cout << x << endl;
	return 0;
}

在这里插入图片描述
在这里插入图片描述
也可以用vector创建多个对象

#include <iostream>
#include <mutex>
#include <vector>
using namespace std;

int main()
{
	mutex mtx;
	int x = 0;
	int n = 10;
	int m;
	cin >> m;
	vector<thread> v;
	v.resize(m);
	//也可以不用reszie
	//vector<thread> v(m);
	for (int i = 0; i < m; i++)
	{  //移动赋值给vector中的线程	
		v[i] = thread([&]() {
		for (int i = 0; i < n; ++i)
			{
				mtx.lock();

				std::cout << this_thread::get_id() << ":" << i << endl;
				std::this_thread::sleep_for(std::chrono::milliseconds(100));
				++x;
				mtx.unlock();
			}
			});
	}
	for (auto& t : v)
	{
		t.join();
	}
	std::cout << x << endl;
	return 0;
}

在这里插入图片描述

原子操作cas

CAS(Compare and Swap)是一种原子操作,用于在多线程环境中实现对共享变量的原子更新。CAS操作包括两个步骤:比较和交换。

在C++中,可以使用std::atomic模板类来实现CAS操作。std::atomic提供了一系列原子操作函数,其中包括compare_exchange_weakcompare_exchange_strong函数,用于实现CAS操作。

下面是一个示例代码,演示了如何使用CAS操作来更新共享变量:

#include <iostream>
#include <atomic>

int main() {
    std::atomic<int> value(0);

    int expected = 0;
    int new_value = 1;

    // 使用CAS操作尝试将value的值从expected更新为new_value
    bool success = value.compare_exchange_weak(expected, new_value);

    if (success) {
        std::cout << "CAS operation succeeded" << std::endl;
    } else {
        std::cout << "CAS operation failed" << std::endl;
    }

    return 0;
}

在上述代码中,我们首先创建了一个std::atomic<int>对象value,并将其初始值设置为0。然后,我们定义了两个变量expectednew_value,分别表示期望的值和要更新的新值。

接下来,我们使用value.compare_exchange_weak(expected, new_value)函数尝试将value的值从expected更新为new_value。如果CAS操作成功,compare_exchange_weak函数会返回true,否则返回false

最后,我们根据CAS操作的结果输出相应的消息。

请注意,CAS操作是一种无锁的并发控制机制,可以避免使用互斥锁带来的性能开销。但需要注意的是,CAS操作并不是适用于所有情况,它在某些场景下可能存在ABA问题等特定限制。因此,在使用CAS操作时需要仔细考虑其适用性和正确性。
并行,
在这里插入图片描述

#include <atomic>
#include <iostream>
#include <mutex>
#include <vector>
#include <atomic>
using namespace std;

int main()
{
	mutex mtx;
	atomic<int> x = 0;
	int n = 1000000;
	int m;
	cin >> m;
	vector<thread> v;
	v.resize(m);
	for (int i = 0; i < m; i++)
	{  //移动赋值给vector中的线程	
		v[i] = thread([&]() {
		for (int i = 0; i < n; ++i)
			{
				//mtx.lock();

				//std::cout << this_thread::get_id() << ":" << i << endl;
				//std::this_thread::sleep_for(std::chrono::milliseconds(100));
				++x;
				//mtx.unlock();
			}
			});
	}
	for (auto& t : v) 
	{
		t.join();
	}
	std::cout << x << endl;
	return 0;
}

在这里插入图片描述

yield

条件没有准备好,让出去cpu

俩个线程交错打印1-100,一个打印奇数,一个打印偶数

//俩个线程交错打印1-100,一个打印奇数,一个打印偶数
int main()
{
	int i = 0;
	int n = 100;
	thread t1([&]() {
		while(i < n)
		{
			while (i % 2 != 0)
			{
				this_thread::yield();
			}
			cout << this_thread::get_id() << ":" << i << endl;
			i += 1;
		}
		});
	thread t2([&]() {
		while(i < n)
		{
			while (i % 2 == 0)
			{
				this_thread::yield();
			}
			cout << this_thread::get_id() << ":" << i << endl;
			i += 1;
		}
		});
	t1.join();
	t2.join();
	return 0;
}

也可以这样,但是这样写会有隐患:中间一旦停顿就会有线程连续运行。或者线程二创建的慢了一点也会有连续运行
在这里插入图片描述
使用wait和unique_wait ,一定是t2先运行

#include <iostream>
#include <mutex>
#include <vector>
#include <atomic>
using namespace std;


//俩个线程交错打印1-100,一个打印奇数,一个打印偶数
int main()
{
	mutex mtx;
	condition_variable cv;
	bool ready = true;
	int i = 0;
	int n = 100;
	thread t1([&]() {
		while(i < n)
		{
			unique_lock<mutex> lock(mtx);
			cv.wait(lock, [&ready]() {return !ready; });

			cout << this_thread::get_id() << ":" << i << endl;
			i += 1;
			ready = true;

			cv.notify_one();
		}
		});
	thread t2([&]() {
		while(i < n)
		{
			unique_lock<mutex> lock(mtx);
			cv.wait(lock, [&ready]() {return ready; });

			cout << this_thread::get_id() << ":" << i << endl;
			i += 1;
			ready = false;

			cv.notify_one();
		}
		});

	this_thread::sleep_for(chrono::seconds(3));
	cout << "t1: " << t1.get_id() << endl;
	cout << "t2: " << t2.get_id() << endl;
	t1.join();
	t2.join();
	return 0; 
}
14664:0
15912:1
14664:2
15912:3
14664:4
15912:5
14664:6
15912:7
14664:8
15912:9
14664:10
15912:11
14664:12
15912:13
14664:14
15912:15
14664:16
15912:17
14664:18
15912:19
14664:20
15912:21
14664:22
15912:23
14664:24
15912:25
14664:26
15912:27
14664:28
15912:29
14664:30
15912:31
14664:32
15912:33
14664:34
15912:35
14664:36
15912:37
14664:38
15912:39
14664:40
15912:41
14664:42
15912:43
14664:44
15912:45
14664:46
15912:47
14664:48
15912:49
14664:50
15912:51
14664:52
15912:53
14664:54
15912:55
14664:56
15912:57
14664:58
15912:59
14664:60
15912:61
14664:62
15912:63
14664:64
15912:65
14664:66
15912:67
14664:68
15912:69
14664:70
15912:71
14664:72
15912:73
14664:74
15912:75
14664:76
15912:77
14664:78
15912:79
14664:80
15912:81
14664:82
15912:83
14664:84
15912:85
14664:86
15912:87
14664:88
15912:89
14664:90
15912:91
14664:92
15912:93
14664:94
15912:95
14664:96
15912:97
14664:98
15912:99
14664:100
t1: 15912
t2: 14664

F:\as\c++\1\thread\Debug\thread.exe (进程 8952)已退出,代码为 0。
按任意键关闭此窗口. . .

share_ptr的线程是安全的吗

std::shared_ptr是C++标准库中提供的智能指针类型之一,用于管理动态分配的对象的生命周期。std::shared_ptr提供了引用计数的机制,可以在多个指针之间共享所管理的对象。

在多线程环境下,std::shared_ptr的线程安全性取决于对其访问的同步措施。如果多个线程同时访问同一个std::shared_ptr对象,而且至少有一个线程对其进行写操作(例如重置、交换、析构等),那么就需要使用适当的同步机制来保证线程安全。

一种常见的同步机制是使用互斥锁(std::mutex)来保护对std::shared_ptr的访问。通过在对std::shared_ptr进行读写操作时加锁和解锁,可以确保在同一时间只有一个线程能够对其进行修改。这样可以避免多个线程同时对引用计数进行修改,从而保证线程安全。

另一种线程安全的方式是使用std::atomic模板类来管理引用计数。std::atomic提供了原子操作,可以确保对引用计数的修改是原子的,从而避免竞争条件和数据竞争。

需要注意的是,只有对std::shared_ptr对象本身的访问需要进行同步,而对于所管理的对象的访问则不需要。std::shared_ptr内部会自动管理引用计数,并在引用计数为零时销毁所管理的对象。

下面是一个示例代码,演示了如何使用互斥锁来保证std::shared_ptr的线程安全:

#include <iostream>
#include <memory>
#include <mutex>
#include <thread>

std::shared_ptr<int> sharedData;  // 共享的std::shared_ptr
std::mutex mtx;  // 互斥锁

void threadFunc() {
    std::lock_guard<std::mutex> lock(mtx);  // 加锁

    if (sharedData) {
        // 对共享数据进行操作
        *sharedData += 1;
        std::cout << "Thread ID: " << std::this_thread::get_id() << ", Value: " << *sharedData << std::endl;
    }
}

int main() {
    sharedData = std::make_shared<int>(0);

    std::thread t1(threadFunc);
    std::thread t2(threadFunc);

    t1.join();
    t2.join();

    return 0;
}

在上述代码中,我们首先定义了一个全局的std::shared_ptr<int>对象sharedData,用于多个线程之间共享数据。同时,我们还定义了一个std::mutex对象mtx,用于保护对sharedData的访问。

threadFunc函数中,我们首先使用std::lock_guard来对mtx进行加锁,以确保同一时间只有一个线程能够访问共享数据。然后,我们对sharedData进行操作,这里只是简单地将其值加1,并输出线程ID和新的值。

main函数中,我们首先使用std::make_shared来创建一个初始值为0的sharedData对象。然后,我们创建两个线程t1t2,分别执行threadFunc函数。最后,我们等待两个线程执行完成,然后退出程序。

通过使用互斥锁来保护对sharedData的访问,我们可以确保在同一时间只有一个线程能够修改共享数据,从而保证了线程安全性。## 懒汉和饿汉的线程安全问题

懒汉模式和饿汉模式的线程安全问题

懒汉模式和饿汉模式是两种常见的单例模式实现方式。在多线程环境下,它们都存在线程安全问题,需要采取适当的措施来解决。

懒汉模式指的是在首次使用时创建单例对象,而饿汉模式指的是在类加载时就创建单例对象。下面分别介绍它们的线程安全问题以及解决方法:

  1. 懒汉模式的线程安全问题:
    在懒汉模式中,如果多个线程同时访问到单例对象还未创建的情况下,可能会导致多个线程都创建了单例对象,从而破坏了单例的唯一性。这是因为懒汉模式没有进行任何的同步措施。

    解决方法:

    • 使用互斥锁(std::mutex)来保护对单例对象的访问,通过在创建单例对象时加锁和解锁,确保只有一个线程能够创建单例对象。但是这种方式会引入锁的开销,可能影响性能。
    • 使用双重检查锁(Double-Checked Locking)来减少加锁的次数。在加锁前后都进行一次检查,以减少锁的开销。但是需要注意的是,双重检查锁需要使用原子操作来保证线程安全。
  2. 饿汉模式的线程安全问题:
    在饿汉模式中,单例对象在类加载时就被创建,因此不存在多个线程同时创建单例对象的问题。但是,如果单例对象的创建过程比较耗时,可能会导致程序启动时的延迟。

    解决方法:

    • 将单例对象的创建过程延迟到首次使用时。可以使用懒汉模式来实现延迟加载,通过在懒汉模式中使用互斥锁或双重检查锁来保证线程安全。

需要注意的是,以上解决方法都是基于互斥锁或原子操作来保证线程安全的。另外,C++11引入了std::call_once函数,可以更方便地实现单例模式的线程安全。std::call_once可以保证在多线程环境下只执行一次指定的函数,可以用来延迟创建单例对象。

互斥锁

  1. 互斥锁示例:
#include <iostream>
#include <mutex>

class Singleton {
private:
    static Singleton* instance;
    static std::mutex mtx;

    Singleton() {}

public:
    static Singleton* getInstance() {
        std::lock_guard<std::mutex> lock(mtx);
        if (instance == nullptr) {
            instance = new Singleton();
        }
        return instance;
    }
};

Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mtx;

int main() {
    Singleton* s1 = Singleton::getInstance();
    Singleton* s2 = Singleton::getInstance();

    std::cout << "s1 address: " << s1 << std::endl;
    std::cout << "s2 address: " << s2 << std::endl;

    return 0;
}

在上述代码中,我们使用了一个静态变量instance来保存单例对象,同时使用了一个静态互斥锁mtx来保护对instance的访问。在getInstance函数中,我们首先使用std::lock_guard来对mtx进行加锁,以确保同一时间只有一个线程能够创建单例对象。然后,我们检查instance是否为空,如果为空,则创建一个新的单例对象。最后,我们返回instance指针。

双检查加锁

  1. 双重检查锁示例:
#include <iostream>
#include <mutex>

class Singleton {
private:
    static Singleton* instance;
    static std::mutex mtx;

    Singleton() {}

public:
    static Singleton* getInstance() {
        if (instance == nullptr) {
            std::lock_guard<std::mutex> lock(mtx);
            if (instance == nullptr) {
                instance = new Singleton();
            }
        }
        return instance;
    }
};

Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mtx;

int main() {
    Singleton* s1 = Singleton::getInstance();
    Singleton* s2 = Singleton::getInstance();

    std::cout << "s1 address: " << s1 << std::endl;
    std::cout << "s2 address: " << s2 << std::endl;

    return 0;
}

在上述代码中,我们在getInstance函数中进行了双重检查。首先,我们检查instance是否为空,如果为空,则加锁并再次检查instance是否为空。这样可以减少加锁的次数,提高性能。注意,双重检查锁需要使用原子操作来保证线程安全。在C++11之前,可以使用std::atomic来实现原子操作。
在这里插入图片描述
在上述代码中,双重检查锁的原子操作是对instance指针的赋值操作。具体来说,在双重检查锁的第二个检查中,当instance为空时,使用互斥锁进行加锁,然后再次检查instance是否为空,并在加锁的临界区内进行赋值操作。这个赋值操作需要保证原子性,以确保只有一个线程能够成功创建单例对象。

在示例代码中,这个原子操作是由互斥锁std::mutex提供的。通过加锁和解锁操作,互斥锁保证了对instance的赋值操作是原子的,即同一时间只有一个线程能够执行这个操作。这样就保证了线程安全性,避免了多个线程同时创建单例对象的问题。

内存栅栏

内存栅栏(Memory Barrier),也称为内存屏障,是一种硬件或软件机制,用于控制指令的执行顺序和内存访问的可见性。它可以确保在某个点之前的所有操作都完成,同时也可以防止在某个点之后的操作提前执行。

内存栅栏有两个主要的作用:

  1. 顺序一致性(Sequential Consistency):内存栅栏可以保证在栅栏之前的所有操作都完成后,栅栏之后的操作才能开始执行。这样可以确保指令的执行顺序和程序的逻辑顺序一致,避免了乱序执行带来的问题。

  2. 内存可见性(Memory Visibility):内存栅栏可以确保在栅栏之前的所有写操作都对其他线程可见。这样可以保证在多线程环境下,一个线程对共享数据的修改在栅栏之前对其他线程可见,避免了数据不一致的问题。

内存栅栏的使用可以提高多线程程序的正确性和性能。在并发编程中,内存栅栏通常与原子操作、互斥锁等同步机制一起使用,以确保线程之间的协调和数据的一致性。

下面是一个简单的例子,说明内存栅栏的作用:

#include <iostream>
#include <atomic>
#include <thread>

std::atomic<int> x = {0};
std::atomic<int> y = {0};
int data = 0;

void thread1() {
    x.store(1, std::memory_order_relaxed);
    std::atomic_thread_fence(std::memory_order_release);
    y.store(1, std::memory_order_relaxed);
}

void thread2() {
    while (y.load(std::memory_order_relaxed) != 1) {
        std::this_thread::yield();
    }
    std::atomic_thread_fence(std::memory_order_acquire);
    std::cout << "data: " << data << std::endl;
    std::cout << "x: " << x.load(std::memory_order_relaxed) << std::endl;
}

int main() {
    std::thread t1(thread1);
    std::thread t2(thread2);
    t1.join();
    t2.join();
    return 0;
}

在这个例子中,有两个线程t1t2。线程t1首先将x设置为1,然后在一个内存栅栏之后将y设置为1。线程t2在一个循环中等待y被设置为1,然后通过一个内存栅栏获取x的值并输出。

在这个例子中,内存栅栏的作用是确保t1中的写操作在t2中可见。在t1中,通过内存栅栏std::atomic_thread_fence(std::memory_order_release),它保证在栅栏之前的所有写操作都在栅栏之后的操作之前完成。在t2中,通过内存栅栏std::atomic_thread_fence(std::memory_order_acquire),它保证在栅栏之前的所有读操作都在栅栏之后的操作之后执行。

如果没有内存栅栏,t2可能会在y.load()之前读取到1,但在x.load()之前读取到0,导致输出结果不一致。通过使用内存栅栏,我们可以确保在t2中读取y之后,x的值已经被t1写入,并且在t2中读取x之前,y的值已经被t1写入,从而保证了输出结果的一致性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/871019.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

c++ 有元

友元分为两部分内容 友元函数友元类 友元函数 问题&#xff1a;当我们尝试去重载operator<<&#xff0c;然后发现没办法将operator<<重载成成员函数。因为cout的输出流对象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作 数了。…

dbm与mw转换

功率值10^(dBm值/10)&#xff0c;单位mW。 对于-5dBm&#xff0c;其功率值为0.3162 mW。 dBm 10 * lg(mW&#xff09;

C++ STL vector 模拟实现

✅<1>主页&#xff1a;我的代码爱吃辣 &#x1f4c3;<2>知识讲解&#xff1a;C之STL &#x1f525;<3>创作者&#xff1a;我的代码爱吃辣 ☂️<4>开发环境&#xff1a;Visual Studio 2022 &#x1f4ac;<5>前言&#xff1a;上次我们已经数字会用…

创建多图层叠加效果的背景与人物图像

引言&#xff1a; 在现代应用程序开发中&#xff0c;图形资源的使用是非常常见的&#xff0c;特别是在用户界面设计中。通过使用TImageList和TGlyph组件的组合&#xff0c;我们可以实现令人印象深刻的多图层叠加效果。本文将介绍如何使用这两个组件来创建背景和人物的多图层叠加…

doubletrouble靶机通关详解

信息收集 漏洞发现 扫目录 发现secret路径 里面有个图 qdPM9.1 网上找找exp 反弹shell http://192.168.0.107//uploads/users/632300-backdoor.php?cmdecho "<?php eval(\$_POST[1]);?>" > 1.php 蚁剑连上去传php-reverse-shell.php 提权 优化shell…

企业微信爆出漏洞,公司员工被迫摸鱼

「作者主页」&#xff1a;士别三日wyx 「作者简介」&#xff1a;CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 「推荐专栏」&#xff1a;对网络安全感兴趣的小伙伴可以关注专栏《网络安全入门到精通》 昨晚加班好好的&#xff0c;突然接到公司通知&…

不可能,绝对不可能

前言 有 2 个月未更了&#xff0c;读者朋友微信留言&#xff1a;“亮哥&#xff0c;最近是不是颓了&#xff1f;好久未更了” 我随即回复&#xff1a; 没想到这时兄弟发来了&#xff1a; 好吧&#xff0c;给大家汇报下近况&#xff0c;不枉大家一直激励我前行。 项目管理 前段时…

MySQL分表实现上百万上千万记录分布存储的批量查询设计模式

我们知道可以将一个海量记录的 MySQL 大表根据主键、时间字段&#xff0c;条件字段等分成若干个表甚至保存在若干服务器中。唯一的问题就是跨服务器批量查询麻烦&#xff0c;只能通过应用程序来解决。谈谈在Java中的解决思路。其他语言原理类似。这里说的分表不是 MySQL 5.1 的…

STM32入门学习之定时器PWM输出

1.脉冲宽度调制PWM(Pulse Width Modulation)是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。PWM可以理解为高低电平的占空比&#xff0c;即输出高电平时间与低电平时间的比值。PWM的应用是否广泛&#xff0c;比如在步进电机的控制中&#xff0c;可以通过P…

WebRTC本地视频通话使用ossrs服务搭建

iOS开发-ossrs服务WebRTC本地视频通话服务搭建 之前开发中使用到了ossrs&#xff0c;这里记录一下ossrs支持的WebRTC本地服务搭建。 一、ossrs是什么&#xff1f; ossrs是什么呢&#xff1f; SRS(Simple Realtime Server)是一个简单高效的实时视频服务器&#xff0c;支持RTM…

【佳佳怪文献分享】通过引导学会行走: 动态环境中的感知四足运动

标题&#xff1a;Learning to Walk by Steering: Perceptive Quadrupedal Locomotion in Dynamic Environments 作者&#xff1a;Mingyo Seo , Ryan Gupta , Yifeng Zhu , Alexy Skoutnev , Luis Sentis , and Yuke Zhu 来源&#xff1a;2023 IEEE International Conference …

Titanic--细节记录二

merge、join以及concat的方法的不同以及相同 相同之处&#xff1a;都用于合并数据。 不同之处&#xff1a; merge主要是基于列的合并。join主要是基于索引&#xff08;行标签&#xff09;的合并。concat可以沿任意轴合并&#xff0c;更灵活。 import pandas as pddf1 pd.Da…

Linux 查看内存使用情况的几种方法

在运行 Linux 系统的过程中为了让电脑或者服务器以最佳水平运行&#xff0c;常常需要监控内存统计信息。 那么今天我们就来看看有哪些方法可以访问所有相关信息并帮助管理员监控内存统计信息。 查看或者获取 Linux 中的内存使用情况既可以通过命令的方式&#xff0c;也可以通…

OptaPlanner笔记6 N皇后

N 个皇后 问题描述 将n个皇后放在n大小的棋盘上&#xff0c;没有两个皇后可以互相攻击。 最常见的 n 个皇后谜题是八个皇后谜题&#xff0c;n 8&#xff1a; 约束&#xff1a; 使用 n 列和 n 行的棋盘。在棋盘上放置n个皇后。没有两个女王可以互相攻击。女王可以攻击同一水…

Python语言基础---选择判断循环结构详解

文章目录 &#x1f340;引言&#x1f340;if语句&#x1f340;if-else语句&#x1f340;if-elif-else语句&#x1f340;for循环&#x1f340;while循环 &#x1f340;引言 在Python编程语言中&#xff0c;选择判断和循环是两个非常重要的概念。它们可以让我们根据条件执行不同的…

分布式应用:Zabbix监控Tomcat

目录 一、理论 1.Zabbix监控Tomcat 二、实验 1.Zabbix监控Tomcat 三、问题 1.获取软件包失败 2.tomcat 配置 JMX remote monitor不生效 3.Zabbix客户端日志报错 一、理论 1.Zabbix监控Tomcat &#xff08;1&#xff09;环境 zabbix服务端&#xff1a;192.168.204.214 …

模型性能的主要指标

主要参数 ROC 曲线和混淆矩阵都是用来评估分类模型性能的工具 ROC曲线&#xff08;Receiver Operating Characteristic curve&#xff09;&#xff1a; ROC曲线描述了当阈值变化时&#xff0c;真正类率&#xff08;True Positive Rate, TPR&#xff09;和假正类率&#xff0…

SAP 收藏夹Favorites介绍,收藏夹导入/导出功能

作为SAP用户&#xff0c;经常需要使用一些事务代码T-Code, 很多时候会因为不常用其中的一些遗忘这个功能&#xff0c;所以这时候分类收藏好不同Module的事务代码到收藏夹里是一个不错的选择。 经常面临的一个场景就是需要变换系统环境&#xff0c;比如从Client A01,去到Client…

springcloud 基础

SprinbCloud微服务简介 架构发展历史 SpringBoot由baiPivotal团队在2013年开始研发、2014年4月发布第一个du版本的全新开源的轻量级框架。 它基zhi于Spring4.0设计&#xff0c;不dao仅继承了Spring框架原有的优秀特性&#xff0c;而且还通过简化配置来进一步简化了Spring应用…

QIIME 2教程. 11元数据Metadata(2023.5)

QIIME 2用户文档. 11元数据 Metadata in QIIME 2 https://docs.qiime2.org/2023.5/tutorials/metadata/ 注&#xff1a;此实例需要一些基础知识&#xff0c;要求完成本系列文章前两篇内容&#xff1a;《1简介和安装Introduction&Install》和4《人体各部位微生物组分析Movin…