也谈线程同步变量

  前段时间看了一下QT中的线程同步变量,这里谈谈我的心得,由于线程同步变量在很多系统和平台中都有涉及,所以下文将更多地从它们的共性方面去讨论。废话少说,进入正文。

QT中主要提供了以下几个同步类:

QMutex

QSemaphore

QWaitCondition

QReadLocker

QWriteLocker 等。

下面就主要探讨一下前三个同步类。

 QMutex类的功能比较简单,主要分为RecursiveNon-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提供对多个资源的保护,适用于生产者和消费者的模式。包括AcquireRelease方法。

 

QWaitCondition,有些平台上也叫Monitor,提供了对多个条件的等待。下面是一个用条件变量实现的生产者和消费者的例子。

代码
void Producer::run()
{
    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是与一个条件联系在一起用的,常见的使用方法如下:

代码
While(true)
{
    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:WaitConditionwait的内部实现一般是先unlock传入的mutex,然后等待,收到信号后再对mutex进行lock操作。这样为编程提供了一些便利,下面将提到。

 

SemaphoreWaitCondition的比较

下表总结了两者的不同 

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的时候要注意的是WaitConditionwake方法在没有其他线程等待的情况下是不起作用的,所以在使用的时候要格外注意,下面是一个qt中的一个例子:

代码
forever {
     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,那么在另一个线程中想对其操作,满足原来的条件的时候就会产生死锁。 

posted on 2010-04-08 21:46  chinese_submarine  阅读(1885)  评论(2编辑  收藏  举报