第二章主要讲的是互斥量和条件变量保证线程同步。
一般,我们把锁创建销毁,加锁解锁用类封装起来。一方面 ,RAII手法保证锁生效期间等于作用域,不会忘记解锁。另一方面,其他的逻辑判断也可以在类中实现。比如加锁后的线程或协程加到睡眠队列中,然后返回主协程;解锁后从队列中弹出,唤醒该协程等。
pthread_mutex_init(&m_Mutex, NULL);
pthread_mutex_destroy(&m_Mutex);
pthread_mutex_lock(&m_Mutex);
pthread_mutex_unlock(&m_Mutex);
互斥器一般希望尽快拿到锁访问解锁,而条件变量是为了等待某个条件成立。需要和mutex结合使用,一般用来实现高层的同步措施比如阻塞队列,倒计时。倒计时用在主线程发起多个子线程,子线程完成一定任务,主线程继续执行。
主要函数,wait()等待计数值变为0.
Mutex lock(mutex);
while(count>0)
{
condition.wait();
}
举个例子:
在应用程序中有4个进程thread1,thread2,thread3和thread4,有一个int类型的全局变量iCount。iCount初始化为0,thread1和thread2的功能是对iCount的加1,thread3的功能是对iCount的值减1,而thread4的功能是当iCount的值大于等于100时,打印提示信息并重置iCount=0。
如果没有条件变量,应该是
thread1/2:
while (1)
{
pthread_mutex_lock(&mutex);
iCount++; 这个变量可以被多个线程看到,所以操作时需要加锁
pthread_mutex_unlock(&mutex);
}
thread4:
while(1)
{
pthead_mutex_lock(&mutex);
if (100 <= iCount)
{
printf("iCount >= 100\r\n"); 通知大家
iCount = 0;
pthread_mutex_unlock(&mutex);
}
else
{
pthread_mutex_unlock(&mutex);
}
}
可以看到4线程不停在轮询,每次都要加锁解锁,不仅CPU浪费,而且影响并发性。可以sleep缓解,但是导致可能大于100还没来得及通知。
条件变量使用如下:
thread1/2:
while(1)
{
pthread_mutex_lock(&mutex);
iCount++;
pthread_mutex_unlock(&mutex);
if (iCount >= 100)
{
pthread_cond_signal(&cond);
}
}
thread4:
while (1)
{
pthread_mutex_lock(&mutex);
while(iCount < 100)
{
pthread_cond_wait(&cond, &mutex);
}
printf("iCount >= 100\r\n");
iCount = 0;
pthread_mutex_unlock(&mutex);
}
当iCount < 100时,会调用pthread_cond_wait。而pthread_cond_wait它会释放mutex,然后等待条件变为真返回。当返回时会再次锁住mutex。因为pthread_cond_wait会等待,从而不用一直的轮询,减少CPU的浪费。在thread1和thread2中的函数pthread_cond_signal会唤醒等待cond的线程(即thread4),这样当iCount一到大于等于100就会去唤醒thread4。从而不致出现iCount很大了,thread4才去处理。
为什么是while不是if, while(iCount < 100)
因为可能存在唤醒和返回之间有时间差,这段时间有可能又被3线程减到小于100,所以需要再加锁判断一次。还有一种解释是虚假唤醒,因为还可能被其他信号唤醒。
为什么必须和互斥量一起使用?
因为有可能线程4在进入循环后,但是还没有执行到wait(),这时就已经大于100发信号了,这样就收不到了。所以加锁后,必须等到wait()执行,解锁,然后才能发信号,这样就不会丢失了。