也谈线程同步变量
前段时间看了一下QT中的线程同步变量,这里谈谈我的心得,由于线程同步变量在很多系统和平台中都有涉及,所以下文将更多地从它们的共性方面去讨论。废话少说,进入正文。
QT中主要提供了以下几个同步类:
QMutex
QSemaphore
QWaitCondition
QReadLocker
QWriteLocker
等。
下面就主要探讨一下前三个同步类。
QMutex类的功能比较简单,主要分为Recursive和Non-Recursive两种方式,Recursive模式即指在同一线程中可以对某个QMutex多次加锁,当然也要同等次数的解锁。而Non-Recursive即指QMutex只能被lock一次,否则视为死锁。下面重点介绍一下这两种模式的异同。
在Recursive模式下需要记录第一次对其进行lock操作的线程id,即ownership(它的所属),如果是同一个线程,则对此Mutex再加一次锁,不会block当前的线程。如果是不同的线程,则需要block当前的线程,此外对该QMutex unlock的线程必须和lock的线程相同。
在Non-Recursive模式下unlock的线程则不需要与lock的线程相同,在这点上有点类似于Semaphore。有的地方说Non-Recursive模式下的Mutex没有记录ownership,那么我猜测Non-Recursive死锁的检测可能就是等待确定的时间,如果超过了该时间没有被解锁则判定为死锁,不知道这种猜测对不对,哪位知道的可以share一下。通过查看QMutex的代码,发现QMutex版本的Mutex记录了线程的Ownership,所以在QMutex被二次加锁时能够立刻检查出死锁。
QSemaphore类提供对多个资源的保护,适用于生产者和消费者的模式。包括Acquire和Release方法。
QWaitCondition类,有些平台上也叫Monitor,提供了对多个条件的等待。下面是一个用条件变量实现的生产者和消费者的例子。
{
qsrand(QTime(0,0,0).secsTo(QTime::currentTime()));
for (int i = 0; i < DataSize; ++i) {
mutex.lock();
if (numUsedBytes == BufferSize)
//缓冲区已满则等待
bufferNotFull.wait(&mutex);
mutex.unlock();
buffer[i % BufferSize] = "ACGT"[(int)qrand() % 4];
mutex.lock();
++numUsedBytes;
//生产完一个,唤醒因为缓冲区为空而等待的消费者
bufferNotEmpty.wakeAll();
mutex.unlock();
}
}
//! [2]
//! [3]
class Consumer : public QThread
//! [3] //! [4]
{
public:
void run();
};
void Consumer::run()
{
for (int i = 0; i < DataSize; ++i) {
mutex.lock();
if (numUsedBytes == 0)
//缓冲区为空,等待
bufferNotEmpty.wait(&mutex);
mutex.unlock();
fprintf(stderr, "%c", buffer[i % BufferSize]);
mutex.lock();
--numUsedBytes;
//消费完一个,唤醒因为缓冲区满等待的消费者
bufferNotFull.wakeAll();
mutex.unlock();
}
fprintf(stderr, "\n");
}
由此看出WaitCondition是与一个条件联系在一起用的,常见的使用方法如下:
{
Do_something();
Mutex.lock();
If (condition)
waitCondition.wati(mutex);
Mutex.unlock();
}
While (true)
{
Do_something();
Mutex.lock();
Chang the condtion;
waitCodtion.wake();
Mutex.unlock();
}
Note:WaitCondition的wait的内部实现一般是先unlock传入的mutex,然后等待,收到信号后再对mutex进行lock操作。这样为编程提供了一些便利,下面将提到。
Semaphore和WaitCondition的比较
下表总结了两者的不同
Semaphores | Condition Variables |
Can be used anywhere in a program, but should not be used in a monitor | Can only be used in monitors |
Wait() does not always block the caller (i.e., when the semaphore counter is greater than zero). | Wait() always blocks the caller. |
Signal() either releases a blocked thread, if there is one, or increases the semaphore counter. | Signal() either releases a blocked thread, if there is one, or the signal is lost as if it never happens. |
If Signal() releases a blocked thread, the caller and the released thread both continue. | If Signal() releases a blocked thread, the caller yields the monitor (Hoare type) or continues (Mesa Type). Only one of the caller or the released thread can continue, but not both. |
总得来说两者是可以互换的,比如WaitCondition虽然没有记录在它上面等待的个数,但是可以通过与一个整数联用来达到与Semaphore相同,如上面的例子中所示。据说WaitCondition内部也可以用Semaphore来实现的。
此外在使用WaitCondition的时候要注意的是WaitCondition的wake方法在没有其他线程等待的情况下是不起作用的,所以在使用的时候要格外注意,下面是一个qt中的一个例子:
mutex.lock();
keyPressed.wait(&mutex);
++count;
mutex.unlock();
do_something();
mutex.lock();
--count;
mutex.unlock();
}
forever {
getchar();
mutex.lock();
// Sleep until there are no busy worker threads
while (count > 0) {
mutex.unlock();
sleep(1);
mutex.lock();
}
keyPressed.wakeAll();
mutex.unlock();
}
上面的代码通过添加一个count变量,防止在其他线程还在工作的情况下被唤醒。由于此处的mutex还用来保护count变量的同步,所以如果QWaitCondition在wait操作时没有对其unlock,那么在另一个线程中想对其操作,满足原来的条件的时候就会产生死锁。