C++ Logo


Advanced search

Re: [std-proposals] Standard library should have a simple 'gate' for multithreading

From: Thiago Macieira <thiago_at_[hidden]>
Date: Fri, 22 Jul 2022 08:24:33 -0700
On Friday, 22 July 2022 02:49:06 PDT Frederick Virchanza Gotham via Std-
Proposals wrote:
> > You're describing a pair of std::binary_semaphore.
> > https://en.cppreference.com/w/cpp/thread/counting_semaphore
> That's too complicated in more than one way:
> (1) It's too complicated in the English terminology used (i.e. talking
> about semaphores and whether they're binary or not -- when all we want
> is a simple gate)

Except that semaphores are actually well-known in threading. They were in fact
the first threading synchronisation primitive I ever learned, before mutexes
even. So layman knowledge of English is not required; knowledge of threading

> (2) It's too complicated in terms of class types and template parameters

What? std::binary_semaphore has no template parameters.

The fact that the cppreference.com documentation mangles it with the much more
complicated counting_semaphore is a website problem, not a design problem. And
besides, this would tell us that your gate class would suffer from the same
problem, like by being implemented by a std::counting_gates template class.

> (3) It's too complicated to be managing two distinct objects instead of one

Maybe, but that's what your gate class is. C++ subscribes to "don't pay for
what you don't need" and at least half the time that I use a semaphore, I use
it in one direction only, where I don't need to wait_for_open and
wait_for_closed. So for those cases, a single binary_semaphore would suffice and
using a gate as you described means it's twice as expensive.

In fact, the majority of cases I've needed a semaphore on, I've actually
needed counting_semaphore, where I either release N threads to do work or wait
for N threads to have independently finished their work.

> I mean really who wants code like as follows?
> pair<binary_semaphore> pbs;
> // main thread
> pbs.first.acquire();
> pbs.second.release();
> // worker thread
> pbs.first.release();
> pbs.second.acquire();

Because often you prepare conditions before starting the threads, so you could

  // main thread:
  prepare conditions
  start thread(s)

  // worker thread(s)
  do work

Single semaphore.

If you need to coordinate the threads' launching, maybe you should also look
at std::barrier, but why do you need that in the first place? The problem with
examples is that they're often contrived and make no sense in isolation.

Ok, so let's suppose you want to pre-start the thread(s) because you're
running on an operating system with slow threading API (Windows), then yes,
you'd need two semaphores:

  // main thread:
  start thread(s)
  prepare conditions

  // worker thread(s)
  do work

So this does look like your code, but really, it's just nomenclature. You
don't need to use std::pair actually (you should NEVER use std::pair in any
new code, NO exceptions).

> And we should also take beginner programmers into account, there are
> some beginners programmers who decide to start out in C++. Give them a
> gate.

Beginners at C++ but experts at threading.

> Messing around with two binary_semaphore's is nonsense, and if you're
> working on a big project with a dozen other programmers, a bug will
> creep in somewhere at some point (you'll get threadlock when somebody
> does "pbs.first.acquire()" instead of "pbs.second.acquire()").

Hence the rule: NEVER use a std::pair. There are no exceptions, zero, nada,

Also: the only valid value for sleep_for is zero. Any non-zero value is wrong
(there are a couple of exceptions for this one, usually associated with
working around an already-existing bad API you can't fix).

Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
   Software Architect - Intel DPG Cloud Engineering

Received on 2022-07-22 15:24:40