浅谈多线程共享数据的保护

多线程环境下,对共享数据的保护(线程互斥)是件很痛苦的事。本文以JUCE类库所提供的CriticalSection临界区及预定义的ScopedLock作用域锁为例,简单谈谈这个问题。

JUCE没有Semaphore(信号量)一说,也没有Mutex(互斥)一说,CriticalSection临界区即相当于互斥。采用临界区的办法来实现多线程环境下对共享数据和共享资源的保护,是通过某个作用域范围内锁对象对临界区对象进行加锁和解锁来实现的。临界区对象被锁对象加锁后,锁对象所位于的作用域内的数据不能再被其他线程读写。代码执行到作用域结束位置时,锁对象自动解锁。

很显然,锁对象的加锁与解锁基于RAII技术。即:同一时间,不管有多少个线程,最多只能有一个线程拥有已锁定的CriticalSection临界区对象,其他线程要拥有此对象,必须等待临界区对象解锁。而对临界区对象的加锁和解锁,通过作用域锁对象来进行(使用锁对象的原因之一是防止直接使用临界区对象调用enter()自我加锁后,忘记或不便于调用exit()来解锁)。当然也可直接用临界区对象调用enter()和exit()来自我加/解锁,但需注意两点:

  1. 已经加锁,则不能再次加锁。
  2. 一个exit()只匹配一个enter()。

拥有已锁定的临界区对象的线程,可以自由读写作用域内的数据而无需担心其他线程的竞争。读写完毕,退出作用域时临界区对象解锁,供其他线程锁定和读写该作用域内的数据。JUCE类库利用此技术完成多线程环境下的代码并发执行并解决竞争和死锁的经典问题。

由上可知,要实现共享数据和资源的互斥与保护,有三个关键点。一是CriticalSection临界区(互斥)对象,二是指明作用域(一对花括号之间的范围),三是使用作用域锁对象(自动对临界区对象加锁和解锁)。锁对象由几个内置的类型重定义来实现,通常情况下无需深究类型重定义的原型及其内部细节,直接使用即可。实际编程中使用最多的是ScopedLock作用域锁。

仅需两步即可实现线程同步时的数据保护:

  1. 类中声明CriticalSection临界区对象,必须是栈对象。即:在作用域之外需提前定义临界区对象。
  2. 类的功能性函数中指明作用域。在作用域的开始处创建一个ScopedLock锁对象,其构造参数为临界区对象。

注意:锁对象也必须是栈对象,并且通常声明为const常对象。

完成上述两个步骤后,系统将确保同一时间只能有一个线程可访问作用域内的数据。并且当代码执行到作用域之外时,CriticalSection对象自动解锁,以方便其它线程锁定并读写作用域内的数据。示例:


// 类中已声明CriticalSection栈对象lock
void beginTest() 
{
    // 临时定义锁对象sl, 用于锁定临界区对象lock
    const ScopedLock sl (lock);

    // 调用函数,该函数中的数据一并被保护
    createTestSound();
    // 其他代码...

    // 该函数执行完毕,此处lock自动解锁
}   

关于锁

JUCE类库中预定义了4个锁对象:

  1. 最常用的ScopedLock作用域锁定
  2. ScopedUnlock取消作用域锁定
  3. ScopedTryLock作用域内尝试锁定
  4. 读写Component对象的主线程UI锁MessageManagerLock

使用作用域锁来锁定CriticalSection对象,可无需直接用CriticalSection对象调用其enter()和exit()等成员函数,比较安全可靠。

关于多线程共享数据的保护,JUCE类库还提供了以下类:


// 作用域范围之外定义一个读写锁(专用于读写操作的临界区对象)
ReadWriteLock  myLock;
 
for ( ; ; )
{
    // 作用域内定义ScopedReadLock的const栈对象,
    // 锁定先前创建的ReadWriteLock互斥对象
    const ScopedReadLock myScopedLock (myLock);  
    // 此处,myLock已锁定
 
    // 其他代码...

    // 此处myLock自动解锁(即:本次for循环自动解锁)
}

除了线程间的数据锁定(保护),JUCE还提供了InterProcessLock进程间锁定。该类的功能与CriticalSection临界区类基本一致,不同处有二:

  1. 临界区类用于线程间的数据保护,而InterProcessLock则用于进程间的数据保护。
  2. InterProcessLock对象使用InterProcessLock::ScopedLockType类的对象来锁定。

本文作者:SwingCoder

如果本文对您有所启发或助益,请微信打赏

创作时间: 2015.03.20 10:48
最后修改: 2017.05.10 15:27

本文版权:UnderwaySoft   共享协议:署名-非商业使用-禁止演绎


上一篇: 多线程编程的几点思考与忠告
下一篇: Linux下使用JUCE类库编程开发

 随机推荐:
Email: underwaySoft@126.com 微信公众号: UnderwaySoft