是否存在使用信号量而不通过 shared_ptr 延长生命周期的危险用例?

Are there use cases in which it dangerous to use semaphores without extending lifetime through shared_ptr?

我目前在以下设置中使用 void futures 进行线程同步:线程 A 将线程 B 提交到消息线程队列以产生线程 A 所需的资源。线程 A 等待资源,但如果它花费的时间太长,然后将在没有资源提供的功能的情况下继续。

对于 futures,对线程 B 的调用如下所示(伪代码):

/* Thread B function signature: void ThreadB(std::promise<void> p); */

std::promise<void> promised_resource;
std::future<void> future_resource {promised_resource.get_future()};
SubmitToMessageThread([p = std::move(promised_resource)]() mutable { ThreadB(std::move(p)); }));
if (future_resource.wait_for(std::chrono::seconds(1)) == std::future_status_ready)
   /* work with resource if thread B has called p.set_value() to indicate the resource is ready */;
else
  /* make do without the resource */

消息线程持续时间比线程 A 长,但线程 B 移动 promise,因此线程 B 可以完全控制承诺的生命周期,并且不应该'即使线程 B 在线程 A 完成后处于 运行,也不会在其销毁后访问 promisepromisefuture 之间的拆分允许每个线程控制它所负责的对象的生命周期。

情况与信号量不同,因为两个线程之间只共享一个对象(semaphore)。最初,我打算为线程 B 提供对 semaphore 的引用。当可以保证线程 B 不会比线程 A 长寿时,这会起作用。但在我的设置中,情况并非如此。这是否意味着除了 semaphore 之外,我还必须使用 shared_ptr(或其他类似机制)?

是的,您负责管理 semaphore 的生命周期,并且当创建 semaphore 的线程可能被另一个使用 semaphore 的线程超过生命周期时必须特别小心。

天真的方法是通过引用传递 semaphore。虽然如果您可以保证引用在线程 B 的生命周期内保持有效,那将是可行的,但在所描述的情况下情况并非如此。

/* Thread B function signature: void ThreadB(std::shared_ptr<std::binary_semaphore>& done); */

auto done {std::make_shared<std::binary_semphore>(0)};
SubmitToMessageThread([&done]() { ThreadB(done); }));
if (done.wait_for(std::chrono::seconds(1)))
    /* work with resource if thread B has called done->release() to indicate the resource is ready*/
else
   /* make do without the resource */

但是线程 B 没有这样的生命周期保证。线程 A 的 wait_for 可能会在线程 B 完成之前过期,并且线程 A 可能会继续完成,导致 semaphore 在线程 B 使用之前被破坏。我们必须管理生命周期,通常使用shared_ptr。比如下面的:

/* Thread B function signature: void ThreadB(std::shared_ptr<std::binary_semaphore> done); */

auto done {std::make_shared<std::binary_semphore>(0)};
SubmitToMessageThread([done]() { ThreadB(done); }));
if (done.wait_for(std::chrono::seconds(1)))
    /* work with resource if thread B has called done->release() to indicate the resource is ready*/
else
   /* make do without the resource */

总而言之,信号量提供了另一个步兵机会,除非仔细管理生命周期。