STL的empty()函数是否线程安全?

11

我有多个线程在修改STL向量和STL列表。
如果容器为空,我想避免使用锁。

下面的代码是否线程安全? 如果items是列表或映射,会发生什么情况?

class A  
{  
    vector<int> items  
    void DoStuff()  
    {  
        if(!items.empty())  
        {  
            AquireLock();  
            DoStuffWithItems();  
            ReleaseLock();  
        }  
     }  
}  

感谢回复。为了澄清问题: 另一个线程将添加项目。没有其他线程会从项目中删除 - 删除仅会在DoStuffWithItems()内部发生,而且只有一个线程调用DoStuff()。 如果另一个线程正在添加内容,则items.empty()返回false是可以的。如果items.empty()导致应用程序崩溃,那就不行了。 - cpp dev
5个回答

8
这要看你的期望是什么。其他答案都正确地指出,通常情况下,标准C++容器不是线程安全的,并且更进一步地,特别是在调用empty和获得锁之间,你的代码没有防范另一个线程修改容器(但这与vector::empty的线程安全无关)。
因此,为了避免任何误解:你的代码不能保证items在该块内为空。 但你的代码仍然有用,因为你只想避免重复创建锁。你的代码不能提供保证,但它可能会避免不必要的锁创建。它并不适用于所有情况(其他线程仍然可以在检查和锁定之间清空容器),但在某些情况下可以使用。如果你只想通过省略冗余锁来进行优化,那么你的代码可以实现这个目标。
只需确保对容器的任何实际访问都受到锁的保护。
顺便说一下,上述内容严格来说是未定义行为:STL实现理论上允许在调用empty时修改mutable成员。这意味着明显无害(因为只读)的empty调用实际上可能会导致冲突。不幸的是,你不能指望假设只读调用与STL容器是安全的。
在实践中,我非常确定vector::empty不会修改任何成员。但是对于list::empty,我就不那么确定了。如果你真的想要保证,那么要么锁定每次访问,要么不使用STL容器。

3

STL 中的容器和算法没有任何线程安全的保证。

所以,不行。


2

正如已经回答的那样,上面的代码不是线程安全的,必须在实际对容器进行任何操作之前进行锁定。 但是,以下代码应该比始终锁定具有更好的性能,我想不出它可能不安全的原因。 这里的想法是锁定可能很昂贵,我们尽可能避免它,只在真正需要时使用。

class A
{
    vector<int> items;  
    void DoStuff()  
    {  
        if(!items.empty())  
        {  
            AquireLock();
            if(!items.empty())
            {
                DoStuffWithItems();  
            }
            ReleaseLock();  
        }
     }
 }  

假设empty()需要在分配的块中检查一个值,但是在调用期间该块被另一个线程重新分配。这将使empty()本身变得不安全。为了使其安全,需要仔细设计原子操作,并且不能保证库的下一个版本不会再次将其改为不安全。 - Mark Ransom
我正在努力理解在那种情况下会出现什么问题。如果另一个线程正在向容器中插入数据,那么这个检查可能会返回true,而不是false,对吗?因此,我们可能会错过循环的一次迭代,但是下一次没有与其他线程竞争的迭代最终会按预期工作。我是否遗漏了一些重要的东西? - Tigran Ghahramanyan
如果进行空检查需要使用指向已分配内存的指针,并且其他线程导致该指针失效,那么你不仅可能得到错误的答案,还可能完全破坏程序。 - Mark Ransom

2
无论empty是否线程安全,按照你现在的写法,代码不会实现你的目标。
class A  
{  
    vector<int> items  
    void DoStuff()  
    {  
        if(!items.empty())  
        {  
            //Another thread deletes items here.
            AquireLock();  
            DoStuffWithItems();  
            ReleaseLock();  
        }  
     }  
}  

更好的解决方案是在使用 items 时(遍历、获取元素、添加元素、检查数量/是否为空等)每次都进行锁定,从而提供自己的线程安全性。因此,首先获取锁,然后检查向量是否为空。

你的观点(//另一个线程在此处删除项目。)很清楚。然而,显然你可以在获取锁之后检查empty()以确保。 - user184968
@skwllsp:有道理。如果empty()是线程安全的,先检查是否为空,然后锁定,再重新检查可能会避免不必要的锁定。 - Brian

1

STL 不是线程安全的,而且还是空的。如果你想让容器变得安全,你必须通过互斥锁或其他同步关闭所有其方法。


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