使STL对象线程安全的标准方法是什么?

4

我需要一些线程安全的STL容器。

基本上,我想每个STL容器对象只需要添加2个方法即可。

.lock()

.unlock()

我可以把它分成几个部分。
.lockForReading()
.unlockForReading()
.lockForWriting()
.unlockForWriting()

这种方式可以允许多个并行读锁,但如果有写锁存在,则同时读写都会被阻塞。

尝试进行写锁定时,需要等待读取锁定信号量降至0。

是否有标准的方法来实现这一点?

我计划的方式是错误的或者短视的吗?

4个回答

6

标准的方法是在构造函数中获取锁,在析构函数中释放锁。这更常被称为资源获取即初始化(RAII)。我强烈建议您使用这种方法,而不是

.lock()

.unlock()

这是不具备异常安全性的。在抛出异常之前,很容易忘记解锁互斥量,导致下一次尝试锁定时死锁。

在 Boost.Thread 库中有几种同步类型将对您有用,特别是boost::mutex::scoped_lock。建议您使用boost::mutex或其等效物,并在每次访问容器时实例化一个boost::mutex::scoped_lock,而不是在要从多个线程访问的任何容器中添加lock()unlock()方法。


1
我认为这与问题无关。 - Diego Sevilla
2
@Sam:不,RAII与此无关。RAII是指何时以及如何锁定和释放互斥锁。他或她正在询问提供标准线程安全性给标准容器的方法是什么。可以添加锁定/解锁(可撤销)或其他方式。 - Diego Sevilla
1
@bobobobo:是的,有了互斥锁的加锁和解锁。Miller是正确的-在这种情况下应该使用RAII来确保解锁互斥锁,如果您选择这条路的话。也就是说,您将拥有某种类型的LockObject Lock(),它将在LockObject被销毁时自动解锁,并不提供显式的Unlock()调用。 - Puppy
好的,我明白了。你不想要一个.lock()方法,因为有些意外情况可能会阻止在块结束时调用.unlock()方法,但是如果由于栈分配变量的对象销毁而导致调用.unlock(),则任何调用.lock()的函数中的早期返回都将保证释放锁。这里提出了一个很好的观点。 - bobobobo
好的答案,措辞不太恰当 =) - bobobobo
显示剩余4条评论

6

这真的很糟糕。外部代码将无法识别或理解您的线程语义,而容器中对象的别名易于获取,使它们成为贫穷的线程安全接口。

线程安全发生在 设计 时间。你不能通过在问题上投掷锁来解决线程安全问题。您可以通过不让两个线程同时写入相同的数据来解决线程安全 - 当然,在一般情况下。但是,除了直接线程同步原语,特定对象没有处理线程安全的责任。

您可以拥有并发容器,旨在允许并发使用。但是,它们的接口与标准容器所提供的接口 极其 不同。例如,容器中的对象别名较少,并且每个单独的操作都被封装。


好的,听起来不错。如果对这些共享队列的读写相对不频繁/不是性能关键,则避免错误对我来说比其他任何事情都更重要——你所说的“并发容器”在哪里? - bobobobo
1
@bobobobo:你可以在英特尔的线程构建块和微软的并行模式库中找到它们 - 都是专有的。如果你勇敢的话,你可能会在谷歌上找到各种实现,或者根据它们公开可用的文档自己编写。 - Puppy
目前,您可以下载 TBB开源软件。 - bobobobo
@bobobobo:哦,我以为它是商业软件。那就做吧,如果你能的话。 - Puppy

2
这有一个标准的方法吗?
没有,这是有原因的。
我计划的方式是错误或者短视的吗?
想要同步访问单个容器对象并不一定是错误的,但是在容器类的接口中放置同步(如DeadMG所说:对象别名等)往往是错误的。
就我个人而言,我认为TBB和concurrent_vector之类的工具可能过于复杂,或者仍然不适合“简单”的同步问题。
我发现通常只需向持有容器的类添加一个(私有)锁对象,并将访问容器对象的2或3个访问模式包装起来,这就足够了,而且对于后续的其他人来说,这样做会更容易理解和维护。

concurrent_vector的重点在于无需担心push_back和读操作的同步问题,这并不是过度设计。但是,concurrent_vector没有.erase()方法,如果你要使用.clear(),你必须意识到这不是线程安全的。因此,它并不像人们原本希望的那样轻松自如。 - bobobobo
此外,即使您使用了concurrent_vector,很可能在更高的级别上仍会存在并发问题,因为不同线程中的函数在修改数据时仍然会相互干扰。因此,Martin的设计建议非常好。 - Sumudu Fernando

-1

Sam:你不需要一个.lock()方法,因为有些意外情况可能会阻止在块结束时调用.unlock()方法,但是如果.unlock()作为栈分配变量的对象销毁的结果被调用,则任何调用.lock()的函数中的早期返回都将保证释放锁。

DeadMG: 英特尔的线程构建块开源)可能是您要寻找的东西。

还有Microsoft的concurrent_vector和concurrent_queue,这已经随Visual Studio 2010一起提供了。


这是一个注释吗? - Sam Miller
这是应该被接受的答案!! - bobobobo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接