三十部分库中的线程安全性

thread safety in thirty part libraries

本文关键字:线程 安全性 三十部      更新时间:2023-10-16


我想使用别人开发的库,我只有库文件,没有源代码。我的问题是:库为类提供了许多功能。类本身不是线程安全的。我想让它线程安全,我想知道这个代码是否工作

// suppose libobj is the class provided by the library
class my_libobj : public libobj {
   // code
};

这只继承自libobj,它可能"工作",也可能"不工作",这取决于类是否是为继承而设计的(至少有一个virtual析构函数(。

无论如何,它不会为你免费购买螺纹安全。最简单的方法是向类中添加互斥对象,并在进入关键部分时锁定这些互斥对象:

class my_obj {
    libobj obj;  // inheritance *might* work too
    boost::mutex mtx;
    void critical_op()
    {
        boost::unique_lock lck(mtx);
        obj.critical_op();
    }
};

(这是一个非常粗粒度的设计,只有一个互斥;如果你知道各种操作的行为,你可能会使它变得更细粒度。正如@dribeas所解释的,它也不是傻瓜式的。(

在一个尚未设计的库中改造线程安全性和BTW,有不同的级别,如果你不满足于序列化对它的所有调用,那么在不知道它是如何实现的情况下,可能是不可能的,即使这样,如果接口足够糟糕,也可能会有问题——例如,请参阅strtok。

如果不了解,这是不可能回答的,至少是类的实际接口。一般来说,答案是否定的。

从实用C++的角度来看,如果该类不是为扩展而设计的,那么每个非虚拟方法都不会被覆盖,因此您可能会得到一些线程安全和非线程安全方法的混合。

即使您决定只在持有锁的情况下进行包装(无继承(并强制委派,这种方法仍然不是在所有情况下都有效。线程安全不仅需要锁定,还需要一个可以实现线程安全的接口。

考虑一下STL中的stack实现,只需添加一层锁定(即使每个方法都是线程安全的(,就不能保证容器上的线程安全。考虑一下几个线程向堆栈中添加元素,两个线程提取信息:

if ( !stack.empty() ) {  // 1
   x = stack.top();      // 2
   stack.pop();          // 3
   // operate on data
}

这里可能会出现很多错误:当堆栈中只有一个元素时,两个线程可能都会执行测试[1],然后按顺序输入,在这种情况下,第二个线程将在[2]中失败(或获得相同的值,但在[3]中失败(,即使在容器中有多个对象的情况下,这两个线程都可以在执行[3]之前执行[1]和[2],在这种情况下,两个线程将使用相同的对象,堆栈中的第二个元素将被丢弃而不进行处理。。。

线程安全需要(在大多数情况下(更改API,在上面的示例中,提供实现为:的bool pop( T& v );的接口

bool stack::try_pop( T& v ) {     // argument by reference, to provide exception safety
   std::lock<std:mutex> l(m);
   if ( s.empty() ) return false; // someone consumed all data, return failure
   v = s.top();                   // top and pop are executed "atomically"
   s.pop();
   return true;                   // we did consume one datum
}

当然,还有其他方法,您可能不会返回失败,而是等待pop操作中的一个条件,该条件通过使用条件变量或类似的东西来保证锁定,直到数据准备就绪。。。

最简单的解决方案是为该库创建一个唯一的线程,并仅从该线程访问该库,使用消息队列传递请求和返回参数。