一 虚假唤醒
什么是虚假唤醒?我们先看以前关于condition_variable的例子
class A
{
public:
// 把收到的消息传入队列
void inMsgRecvQueue()
{
for (size_t i = 0; i < 1000; ++i)
{
cout << "收到消息,并放入队列 " << i << endl;
std::unique_lock<std::mutex> my_uniq(my_mutex);
msgRecvQueue.push_back(i);
my_condi.notify_one(); // 我们尝试把my_condi.wait()唤醒,通知它。
}
cout << "消息入队结束" << endl;
}
// 从队列中取出消息
void outMsgRecvQueue()
{
while (true)
{
// 获取my_mutex,并加锁
std::unique_lock<std::mutex> my_uniq(my_mutex);
my_condi.wait(my_uniq, [this] {
if (!msgRecvQueue.empty())
return true;
else
return false;
});
int command = msgRecvQueue.front();
msgRecvQueue.pop_front();
my_uniq.unlock();
// 其它业务
cout << "出队列:" << command << endl;
}
}
}
虚假唤醒就是:当条件变量的wait()函数被唤醒时,它的条件并没有满足(第二个参数,或者说条件表达式返回false),这就是虚假唤醒。
例如,在上述代码中,如果入队列成功后,将my_condi.notify_one(); 调用多次,并且在出队列的wait()函数中,没有第二个参数(条件表达式),那么就可能存在一种情况,就是队列已经为空,但是出队列的线程仍然会从队列中取数据。再比如,如果有多个取数据的线程,一个入队列的线程。当插入一条数据时,调用notify_all()通知其它线程去取。可能一个线程将数据取走后,其它的线程取的时候,队列已经为空。
总面言之,就是上述代码中,wait()被唤醒的时候,队列中实际没有数据。 可上面的代码不会发生这样的情况,因为它加了条件表达式进行判断,不会被虚假唤醒。
避免虚假唤醒:wait()中要有第二参数(lambda)并且能正确的判断公共数据是否存在。
二 线程池
假设有以下情况:
A 你在线一个服务端程序,每当一个客户端连接,就必须创建一个新的线程为它服务。
B 你做一个网络游戏,每当有游戏玩家进行充值时,你必须分配新的线程为其服务。
以上情景会面临这样的问题,如果客户端或者充值的玩家数量有很多,你的系统就分配不出这么多线程资源,那么程序就有可能会奔溃。这时,线程池就能解决这样的问题。线程池就是把一堆线程放在一起统一管理,通过循环利用其中的第一个线程进行任务调度。
线程池的实现方式:在程序一启动时就创建一定数量的线程,这个数量一般能由系统的API提供。
三 线程数量谈
- 线程开的数量的极限是2000个左右。
- 创建线程使用某些API的接口建议的个数
- 创建线程数量根据你需要完成业务的数量。比如,你有100用户同时充值,你可以开110个线程。
- 自己测试,实践,测试多少线程效率最高。尽量不要超过500个。