Существуют ли случаи использования, в которых использование семафоров опасно без продления времени жизни через shared_ptr? - PullRequest
0 голосов
/ 22 апреля 2020

В настоящее время я использую void futures для синхронизации потоков в следующем параметре: поток A отправляет поток B в очередь потока сообщений, чтобы создать ресурс, необходимый для потока A. Поток A ожидает ресурс, но сдается, если он принимает слишком долго и будет продолжаться без функциональности, которую предоставил бы ресурс.

Для фьючерсов вызов потока 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 имеет полный контроль над временем жизни обещания и не должен подвергаться риску доступа к promise после его уничтожения даже если поток B работает после завершения потока A. Разделение между promise и future позволяет каждому потоку контролировать время жизни объекта, за который он отвечает.

С семафорами ситуация другая, поскольку существует только один объект ( semaphore) разделенный между двумя потоками. Первоначально я собирался предоставить потоку B ссылку на semaphore. Это будет работать, когда есть гарантия, что поток B не переживет поток A. Но в моем случае это не так. Означает ли это, что я должен использовать shared_ptr (или другой подобный механизм) в дополнение к semaphore?

1 Ответ

0 голосов
/ 22 апреля 2020

Да, вы несете ответственность за управление временем жизни 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 может go завершиться, что приведет к разрушению 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 */

Таким образом, семафоры предоставляют еще одну возможность использования огнестрельного оружия, если с жизненным циклом не управлять тщательно.

...