三十部分库中的线程安全

发布于 2024-11-01 19:58:10 字数 219 浏览 1 评论 0原文


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

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

I want to use a library developed by someone else, of which I only have the library file, not the source code. My question is this: the library provides a class with a number of functionalities. The class itself is not thread safe. I wanted to make it thread safe and I was wondering if this code works

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

对不⑦ 2024-11-08 19:58:10

它仅继承自 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 所解释的那样,这也不是万无一失的。)

This only inherits from libobj, which may or may not "work" depending on whether the class was designed for inheritance (has at least a virtual destructor).

In any case, it won't buy you thread-safety for free. The easiest way to get that is to add mutexes to the class and lock those when entering a critical section:

class my_obj {
    libobj obj;  // inheritance *might* work too
    boost::mutex mtx;

    void critical_op()
    {
        boost::unique_lock lck(mtx);
        obj.critical_op();
    }
};

(This is very coarse-grained design with a single mutex; you may to able to make it more fine-grained if you know the behavior of the various operations. It's also not fool-proof, as @dribeas explains.)

泡沫很甜 2024-11-08 19:58:10

如果您不满足于仅仅序列化全部 调用它,即使这样,如果接口足够糟糕,也可能会出现问题——例如参见 strtok。

Retrofitting thread safety -- and BTW, there are different level -- in a library which hasn't be designed for is probably impossible without knowing how it has be implemented if you aren't content with just serializing all calls to it, and even then it can be problematic if the interface is bad enough -- see strtok for instance.

旧人 2024-11-08 19:58:10

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

从实际的 C++ 角度来看,如果该类未设计为可扩展,则每个非虚拟方法都不会被重写,因此您最终可能会得到一些线程安全和一些非线程安全方法的混合。

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

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

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 操作中的一个条件上,通过使用条件变量或类似的东西,保证锁定直到数据准备好......

This is impossible to answer without knowledge of at least the actual interface of the class. In general the answer would be no.

From the practical C++ point of view, if the class was not designed to be extended, every non-virtual method will not be overriden and as such you might end up with a mixture of some thread-safe and some non-thread safe methods.

Even if you decide to wrap (without inheritance) and force delegation only while holding a lock, the approach is still not valid in all cases. Thread safety requires not only locking, but an interface that can be made thread safe.

Consider a stack implementation as in the STL, by just adding a layer of locking (i.e. making every method thread safe, you will not guarantee thread safety on the container. Consider a few threads adding elements to the stack and two threads pulling information:

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

There are a number of possible things that can go wrong here: Both threads might perform test [1] when the stack has a single element and then enter sequentially, in which case the second thread will fail in [2] (or obtain the same value and fail in [3]), even in the case where there are multiple objects in the container, both threads could execute [1] and [2] before any of them executing [3], in which case both threads would be consuming the same object, and the second element in the stack would be discarded without processing...

Thread safety requires (in most cases) changes to the API, in the example above, an interface that provides bool pop( T& v ); implemented as:

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
}

Of course there are other approaches, you might not return failure but rather wait on a condition in a pop operation that is guaranteed to lock until a datum is ready by making use of a conditional variable or something alike...

南街九尾狐 2024-11-08 19:58:10

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

The simplest solution is to create a single thread uniquely for that library, and only access the library from that thread, using message queues to pass requests and return parameters.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文