[HN Gopher] std::latch
       ___________________________________________________________________
        
       std::latch
        
       Author : nickysielicki
       Score  : 70 points
       Date   : 2021-05-20 05:49 UTC (17 hours ago)
        
 (HTM) web link (en.cppreference.com)
 (TXT) w3m dump (en.cppreference.com)
        
       | threatripper wrote:
       | Why does it use std::ptrdiff_t for counting instead of int?
        
         | Google234 wrote:
         | Int is not large enough for 64 bit system. ptrdiff Is always
         | the correct size and is signed unlike size_t
        
           | threatripper wrote:
           | As far as I can see it's just used as a counter and doesn't
           | really relate to pointers in any way. Why is int not large
           | enough for this use case?
           | 
           | EDIT: Answer from the developers here
           | https://stackoverflow.com/a/45873222 it's about compatibility
           | with other types used for related purposes.
        
             | amelius wrote:
             | What if you were counting bits instead of bytes?
        
               | smithza wrote:
               | I have made a point of using uint16_t, uint32_t, int16_t,
               | int32_t, etc. to be explicit. Some don't like the look,
               | but it is explicit and helpful for me.
        
               | threatripper wrote:
               | Bits don't appear in array sizes as we use bytes as
               | smallest addressable unit on all relevant platforms.
        
               | amelius wrote:
               | But you could imagine a vector of booleans which you can
               | subscript per boolean.
        
               | threatripper wrote:
               | Imaginable: Yes, Relevant: No
        
             | dgrunwald wrote:
             | There are 64 bit systems where "long int" still is only 32
             | bits. (most importantly: Windows x64)
             | 
             | In general, the C integer types cannot be relied upon;
             | typedefs like uint32_t, size_t, ptrdiff_t should always be
             | preferred.
        
               | mpfundstein wrote:
               | so why not use uint64_t ? makes semantically more sense,
               | doesn't it?
        
               | threatripper wrote:
               | Yes, i see this as a shortcoming of C. Too many
               | assumptions were crammed into the "int" size.
        
         | RJIb8RBYxzAMX9u wrote:
         | Probably because ssize_t is not part of the C++ standard.
        
           | codewiz wrote:
           | Some rationale against ssize_t is given here:
           | http://www.open-
           | std.org/jtc1/sc22/wg21/docs/papers/2018/p122...
        
         | Glavnokoman wrote:
         | why would it? More so I do not see why ptrdiff_t instead of
         | size_t.
        
           | gjulianm wrote:
           | ptrdiff_t is signed, while size_t is unsigned. A size is only
           | positive, a difference might be positive or negative, so you
           | avoid overflow problems.
        
             | Glavnokoman wrote:
             | which difference?
        
           | bonzini wrote:
           | Probably so that implementations can use the spare bit for a
           | flag, such as "is anyone waiting on this".
        
         | nickysielicki wrote:
         | https://stackoverflow.com/a/45873222
         | 
         | Answered by someone on the WG here.
        
           | threatripper wrote:
           | The argument is to use the same type as
           | vector<thread>::difference_type to avoid surprises.
        
       | Agentlien wrote:
       | That's a really handy thing, actually.
       | 
       | I wasn't following the C++20 development like I usually do. A
       | combination of no longer working with C++ and feeling a bit
       | disillusioned by the long wait for Concepts and Modules. I did
       | read a summary of all language proposals which made it in, but
       | this made me realize I didn't read up on the changes to the
       | standard library.
        
       | banachtarski wrote:
       | What a weird article to post. Why not std::barrier, or
       | std::binary_semaphore, or std::counting_semaphore which is also
       | new as of C++20
        
         | dgellow wrote:
         | People find a cool thing online and share it. Not weirder than
         | sharing a specific wikipedia page.
        
           | user-the-name wrote:
           | It is a bit weird too how HN keeps posting random wikipedia
           | pages.
        
       | terryf wrote:
       | Wow, looking at the code in the example, this looks almost
       | nothing like the C++ I used to write 15 years ago. The language
       | really has evolved massively and it seems in a good direction.
        
         | ncmncm wrote:
         | Yes, C++ has got quite a lot more fun to use. C++20 is almost
         | as different from C++11 as C++11 was from C++98.
         | 
         | If you make a point to always use the newest features, where
         | there is a choice, and really put the type system to work for
         | you, programs generally run right the first time, once the
         | compiler is satisfied. In the past ten years I have spent more
         | time filing compiler bug reports than debugging C++ memory
         | usage errors.
         | 
         | It is amazing how many people are all up-to-date on what is
         | new. I think people who complain about C++ on HN must be
         | complaining about a much older version of the language.
        
         | vaylian wrote:
         | Agreed. Can someone explain the following?
         | 
         | std::string product{"not worked"};
         | 
         | Searching for "curly braces C++ string" is not really
         | productive.
         | 
         | I'm also curious what
         | 
         | [&](job& my_job) { }
         | 
         | means.
        
           | sudoankit wrote:
           | string product{"not worked"} is initializing the string
           | product to "not worked".
           | 
           | It's the same as std::string product; product = "not worked";
           | 
           | [&](job& my_job) { } is a lambda expression. & is capturing
           | the variable by reference. my_job is the parameter being
           | passed which is a pointer of type job.
           | 
           | Please check
           | https://en.cppreference.com/w/cpp/language/lambda and
           | https://docs.microsoft.com/en-us/cpp/cpp/lambda-
           | expressions-... for more.
        
             | quietbritishjim wrote:
             | > It's the same as std::string product; product = "not
             | worked";
             | 
             | It's not exactly the same. The original called the
             | converting constructor std::string::string(const char*).
             | Your example calls the std::string default constructor,
             | then the assignment std::string::operator=(const char*).
             | Maybe you didn't mean literally the same, but where trying
             | to illustrate the rough meaning, but the parent commenter
             | said they were familiar with older versions of C++ so I
             | think they'd already be familiar with converting
             | constructors.
             | 
             | It might be more enlightening to say that all of the
             | following are equivalent:                   std::string
             | product{"not worked"};         std::string product("not
             | worked");         std::string product = "not worked";
             | std::string product = std::string("not worked");
             | 
             | (I'm 90% sure about the last one but can't find
             | documentation for it at the moment.) None of them call the
             | copy constructor std::string::string(const std::string&) or
             | copy assignment operator std::string::operator=(const
             | std::string&), although in older versions of the C++
             | standard the last two required that the relevant assignment
             | operators (std::string::operator=(const char*) and
             | std::string::operator=(const std::string&) respectively) to
             | be accessible even though it wasn't called.
             | 
             | For other combinations of types, these different syntaxes
             | are not equivalent. For example, uniform initialisation
             | (with the braces) won't allow narrowing conversions, such
             | as short to int or double to float.
        
               | sudoankit wrote:
               | You're right, I was being sloppy and gave a rough
               | example. My bad.
        
               | im3w1l wrote:
               | The last one did copy in the olden days, but now with
               | copy elision it does not. You can verify this using -fno-
               | elide-constructors
        
             | kleiba wrote:
             | _> It 's the same as std::string product; product = "not
             | worked";_
             | 
             | Not a C++ person, so please forgive my ignorance, but what
             | is the difference between the above and sth. like:
             | std::string product = "not worked";
             | 
             | or                   std::string product("not worked");
             | 
             | (Are these even legal C++ statements and, if not, why not?
             | ;-))
        
               | MakersF wrote:
               | In practical terms, none. They are the same, and the same
               | as using the curly braces.
               | 
               | From the POV of the standard, those are different kinds
               | of initializations. C++ has like 12 different ways of
               | initializing variables, and the differences are quite
               | confusing, but in practical terms in my experience I
               | never had to care too much beside making sure native
               | types are initialized to some specified value.
               | 
               | You can probably find some talks on YouTube talking about
               | the initializations of c++, and 1h30m is probably not
               | enought to cover all the details :')
        
               | kleiba wrote:
               | Yeah, that sounds like C++, I suppose. Thanks very much.
        
               | klibertp wrote:
               | Both are valid, and were the only ways of initializing
               | objects before C++11, I think.
               | 
               | Brace initialization has an advantage in that it allows
               | you to initialize compound values, like containers and
               | structs, even if they're nested:
               | std::map<int, std::string> m = { // nested list-
               | initialization                {1, "a"},
               | {2, {'a', 'b', 'c'} },                {3, s1}         };
               | 
               | Ref: https://en.cppreference.com/w/cpp/language/list_init
               | ializati... https://en.cppreference.com/w/cpp/language/ag
               | gregate_initial...
        
               | im3w1l wrote:
               | How compile-time expensive is this? I guess it has to
               | potentially consider a lot of constructor combinations?
        
               | mellery451 wrote:
               | it's also generally preferred because it can avoid
               | certain narrowing conversions in construction: https://is
               | ocpp.github.io/CppCoreGuidelines/CppCoreGuidelines...
        
               | kleiba wrote:
               | I see, that's interesting.
        
               | dmitrykoval wrote:
               | Both are legal statements.
               | 
               | The second one is a direct initialization, invoking
               | corresponding constructor.
               | 
               | The first one first invokes default constructor and then
               | copy or move assignment depending on rvalueness of the
               | arg.
        
               | kleiba wrote:
               | Thanks very much for the explanations, folks! One more
               | follow-up question: what are reasons to prefer:
               | std::string product;           product = "not worked";
               | 
               | i.e., separate declaration and initialization, as
               | suggested by the grand-parent?
        
               | dmitrykoval wrote:
               | For this particular example direct initialization i.e.
               | std::string product("not worked"); would be preferred, as
               | you end up using one call to constructor instead of two:
               | default constructor followed by the move assignment.
               | 
               | https://en.cppreference.com/w/cpp/language/direct_initial
               | iza... https://en.cppreference.com/w/cpp/language/move_as
               | signment
        
               | kleiba wrote:
               | Thank you very much.
               | 
               | What are examples of situations where the other pattern
               | would be preferable?
        
               | Ticklee wrote:
               | afaik the only time you are really supposed to use
               | uninitialized values is when you are planning on reading
               | it in from some stream.
               | 
               | otherwise you should really avoid it.
        
           | 72deluxe wrote:
           | Curly braces is an "initializer list". Stroustrup covers it
           | in the blue C++ book he wrote about C++11.
        
           | nspattak wrote:
           | What you need to search for is "list initialization" (eg http
           | s://en.cppreference.com/w/cpp/language/list_initializati...)
           | . I understand that it is not the obvious thing to do. If you
           | search for c++11, it was one of the features that were
           | introduced then. lambda expressions were also introduced in
           | c++11 but are also found in other languages so they are
           | easier to understand for non c++ people.
        
           | ktpsns wrote:
           | The first one is a curly braces initializer.
           | 
           | The second one is a lambda function / closure over my_job.
        
           | dataflow wrote:
           | It's called uniform initialization syntax (aka "brace
           | initialization" aka list-initialization [1]). tl;dr is
           | everyone loves it due to the terseness, but I recommend
           | against it. It's kind of like a forced cast/coercion in some
           | ways (and it also looks ugly). Also be careful with
           | parentheses since C++20; if they invoke implicit (aggregate?)
           | constructors that would've traditionally needed {}, I think
           | you can also run into similar casting issues. I just ran into
           | this one a couple days ago; I haven't narrowed it down yet,
           | but it seemed to be due to this.
           | 
           | The second one is a lambda (which, if you're not familiar
           | with the term, are anonymous functions/functors); the entries
           | in the initial brackets define the captured variables (if
           | any), and whether they're captured by reference (with
           | ampersand) or by value (without).
           | 
           | [1] https://en.cppreference.com/w/cpp/language/list_initializ
           | ati...
        
             | gpderetta wrote:
             | It is in fact not a forced cast, unlike T() initialization
             | that can indeed be a cast.
             | 
             | Uniform initialization is great, except for the unfortunate
             | interaction with initialized_lists (yes, we can't have nice
             | things), but if you do not have an initializer_list
             | constructor in your class you do not have to worry.
        
               | dataflow wrote:
               | > It is in fact not a forced cast
               | 
               | Why do you say this when you can explicitly see I
               | _specifically made sure_ to avoid claiming it _is_ in
               | fact a forced cast, and instead I said it is _kind of
               | like_ a forced cast? Clearly I meant something other than
               | that it was actually a forced cast, right?
               | 
               | > if you do not have an initializer_list constructor in
               | your class you do not have to worry.
               | 
               | Which is precisely an example of my point about it being
               | problematic. How would you go about this when you don't
               | know everything about a class? Like, say, in a template?
               | And how do you prevent your code from silently breaking
               | if a class you use later adds an initializer_list
               | constructor?
               | 
               | > Uniform initialization is great
               | 
               | I disagree. It's awful. It's just a minor cosmetic change
               | (which we can call an "improvement" for the sake of
               | argument, though I think that's also dubious and the
               | syntax is just ugly) that introduces pitfalls in the
               | actual semantics of your program. That's not a great
               | trade-off; it's a terrible one.
        
               | qalmakka wrote:
               | > a minor cosmetic change (which we can call an
               | "improvement" for the sake of argument, though I think
               | that's also dubious and the syntax is just ugly) that
               | introduces pitfalls in the actual semantics of your
               | program
               | 
               | std::int64_t i64 { 44 * 44 * 44 }; std::int8_t i8 = i64;
               | // perfectly fine std::int8_t i8_2 { i64 }; // refuses to
               | narrow the int
               | 
               | uniform initialization resolves lots of headaches, like
               | T() being ambiguous at times with functions, narrowing,
               | etc.
        
               | dataflow wrote:
               | I don't see failure on i64 -> i8 as beneficial here; it's
               | harmful if anything. Integer conversions already happen
               | in all sorts of constructs. Which is why compilers have
               | warnings for them. If you don't like them, you can enable
               | warnings (or even set them to errors if you want to
               | outright prohibit them). If you think they should happen
               | implicitly then there's nothing particularly interesting
               | about construction sites vs. assignments or really
               | anything else; there's no reason integer conversions
               | should be treated specially for construction sites. If
               | anything, that would make you feel safer than is actually
               | warranted.
               | 
               | With T() being ambiguous, you can already syntactically
               | disambiguate. With extra parentheses or whatever other
               | construct the case may warrant. As people have been doing
               | all these years. Again, it's just a minor syntactic
               | inconvenience.
        
           | going_ham wrote:
           | >std::string product{"not worked"}; This is string
           | initialization. Now product = "not worked".
           | 
           | >[&](job& my_job) { } A lambda function that takes in a
           | reference to job!
           | 
           | C++ has definitely evolved but the package management is
           | still difficult unlike cargo!
        
             | Fronzie wrote:
             | With Conan, vcpkg
             | (https://devblogs.microsoft.com/cppblog/all-vcpkg-
             | enterprise-...) things are getting better.
             | 
             | For personal projects, vcpkg with manifests is a breeze to
             | use.
        
       | quietbritishjim wrote:
       | See also: std::latch vs std::barrier [1]
       | 
       | In short (copied from that answer):
       | 
       | * Barriers are useful when you have a bunch of threads and you
       | want to synchronise across of them at once, for example to do
       | something that operates on all of their data at once.
       | 
       | * Latches are useful if you have a bunch of work items and you
       | want to know when they've all been handled, and aren't
       | necessarily interested in which thread(s) handled them.
       | 
       | [1] https://stackoverflow.com/a/62631294
        
         | quietbritishjim wrote:
         | To add to this: I don't get why C++ distinguishes a latch from
         | a barrier. Both of them are effectively a counter with the
         | ability to decrement that counter (in a thread safe way) and
         | the ability to wait until the value reaches zero (or return
         | immediately if it is already zero). The only difference is that
         | is a latch does those things separately from different threads
         | while a barrier does them both at once in every thread. That's
         | even reflected in the similar method names: .count_down() and
         | .wait() on latch, and .count_down_and_wait() on barrier. Why
         | not make all three methods be methods of one class, which
         | simplifies the API and potentially allows other use cases?
        
           | nly wrote:
           | Almost certainly the answer is one can be implemented more
           | efficiently than the other using a particular API on a
           | particular platform.
        
       | ncmncm wrote:
       | Thanks, I wrote the sample code for the std::latch and
       | std::barrier pages. [ _Unfortunately you can 't run them in
       | place, because the compiler backing it is too old._ I am wrong!
       | Now you can.] But you can paste them into Godbolt.org and run
       | there, given a bit of compiler link-line fu.
       | 
       | std::barrier has extra knobs that I didn't discover a good use
       | for, or would have used them in the sample. Enlightenment
       | welcome.
       | 
       | This might be a good place to mention: cppreference.com is
       | curated by ISO Standard C++ committee participants.
       | (Cplusplus.com, by contrast, is very poorly maintained.) If
       | cppreference disagrees from the Standard, it is likely that the
       | Standard will soon be amended to match.
       | 
       | Speaking of Godbolt: it enforces a limit of three spawned
       | threads, but the Gcc and Clang thread sanitizers use up a thread.
       | So if you want to try the samples on Godbolt under the thread
       | sanitizer, you have to reduce the problem sizes to two. Learned
       | that the hard way.
        
         | kitd wrote:
         | FWIW, I did a lot of C++ in the 1990s, then went to Java/Go for
         | about 15 years. I had to come back to C++ briefly about 5 years
         | ago. cppreference was a total godsend!
        
         | diath wrote:
         | > Unfortunately you can't run them in place, because the
         | compiler backing it is too old.
         | 
         | Are you sure you've not changed the dropdown? I just made some
         | changes to the example, hit "run this code", hit "run", and the
         | output updated accordingly.
        
           | ncmncm wrote:
           | Oops, you're right -- they updated compiler support! Good
           | news.
        
       | testific8 wrote:
       | I was looking to use this feature, alongside some other C++20,
       | but I was dissapointed to see that it wasn't supported yet by my
       | compiler. So hopefully that gets supported soon. Until then a
       | condition variable and an atomic<bool> will do the job.
       | 
       | I am looking forward to c++23 and beyond. Hopefully they will
       | eventuall add fixed-point arithmetic support.
        
       | klodolph wrote:
       | So many threading primitives have different names on different
       | platforms. This is basically Go's WaitGroup, except the C++ latch
       | can't be incremented (it's otherwise identical). I wonder why
       | that is?
        
         | vinkelhake wrote:
         | It's CountDownLatch in Java.
        
           | brundolf wrote:
           | Cue the jokes about Java name length
        
             | signa11 wrote:
             | not that per-se, but is there a CountUpLatch(...) as well
             | ;)
        
             | skocznymroczny wrote:
             | I like these names. Maybe it's a pain if you're using
             | something like vim to program, but in a typical Java IDE or
             | VSCode you'd just write CDL, ctrl+space, enter.
        
               | siscia wrote:
               | Actually vim comes with an handy autocomplete, that use a
               | very stupid heuristic, but it works wonderfully.
               | 
               | I still miss usint Ctrl-P when working with VS Code.
        
         | signa11 wrote:
         | /// -----------------------------------------------------------
         | -----------------         /// latches are great for multi-
         | threaded tests with following sequence of steps         ///
         | 1. setup test data         ///    2. create a latch         ///
         | 3. create test threads each decrementing the latch-count
         | ///    4. when all threads have reached the latch, they are
         | unblocked.         ///         /// sligtly verbose commented
         | code to illustrate this         void foo()         {
         | /// -----------------------------------------------------------
         | ---------                 /// create a latch with non-zero-
         | count                 unsigned const thread_count=...;
         | std::latch done(thread_count);
         | my_data data[thread_count];
         | std::vector<std::jthread> threads;                          ///
         | ---------------------------------------------------------------
         | -----                 /// threads decrement count as they are
         | created                 for(unsigned i=0;i<thread_count;++i)
         | threads.push_back(std::jthread([&,i]{
         | data[i]=make_data(i);
         | done.count_down();
         | do_more_stuff();                         }));
         | /// -----------------------------------------------------------
         | ---------                 /// others wait for latch to be
         | signalled, when the count reaches zero,                 ///
         | latch is permanently signalled, and threads are woken
         | done.wait();                 process_data();         }
         | 
         | have fun ?
        
           | quietbritishjim wrote:
           | I realise this was just a quick example, but just to be
           | clear, the latch is tied to _the number of work items_ not
           | _the number of threads_. If you had a thread pool (or single
           | thread) with a queue, you can happily push any number of work
           | items on to it and use the latch to wait until they 're all
           | complete, regardless of which threads they're handled in (or
           | even if the jobs are interleved with other jobs from some
           | other source).
        
             | signa11 wrote:
             | yes ofcourse!
        
           | klodolph wrote:
           | I'm just saying in Go, the equivalent construct (WaitGroup)
           | does not require you to figure out the count ahead of time.
           | There's an idiom of:                   var wg sync.WaitGroup
           | wg.Add(1)         go func() {             defer wg.Done()
           | }()              wg.Wait()
           | 
           | The middle part of the block can be done freely, you don't
           | have to count the threads beforehand.
        
             | signa11 wrote:
             | i see. wouldn't you have to do a                  ...
             | wg.Add(1)        ...
             | 
             | for every thread that you are about to create ?
        
               | kitd wrote:
               | That's a common idiom if the number of threads is
               | dynamic.
        
               | signa11 wrote:
               | into the wait-group, the number of threads go right ?
        
         | dgellow wrote:
         | There is always std::barrier:
         | https://en.cppreference.com/w/cpp/thread/barrier.
         | 
         | The C++ stdlib terminology is always so weird.
        
           | jcelerier wrote:
           | Discussion on naming from a few months ago :
           | https://news.ycombinator.com/item?id=25902827
        
         | gpderetta wrote:
         | Likely minimalism. It can be implemented more efficiently if it
         | can only count down. It is a low lever synchronization
         | primitive, you can build more complex stuff on top of it.
        
         | Glavnokoman wrote:
         | Yep. C++ is not good at naming things somehow. But bringing up
         | Golang in a discussion about naming is just ridiculous. Unless
         | you want to show that C++ is not actually the worst.
        
           | msk-lywenn wrote:
           | Is go worst than rust at naming? It took me a while to get
           | used to Cow and Arc... (not to mention "fn" which to me will
           | always be the french far right political party)
        
             | iudqnolq wrote:
             | I like that Rust has optimized certain bits for ease of
             | intermediate use. Sure those could be EitherOwnedOrBorrowed
             | and AtomicReferenceCountedPointer, but that'd be annoying
             | to type once you know them. They're also not hard to
             | remember if you learn the acronyms (for me, at least).
        
           | zambal wrote:
           | To me, the parent poster didn't imply that golang's naming is
           | better, they just wondered why there's a lack of naming
           | conventions regarding threading in general.
        
             | Glavnokoman wrote:
             | Ah! Thanks! You're right. I did not read it this way...
        
         | junon wrote:
         | Usually such guarantees allow the implementation to use
         | something other than atomics (e.g. memory barriers) for
         | avoiding data races, which are more performant and can avoid
         | contention/context switching/spinning.
         | 
         | I too would love to know the specific reasoning though.
        
       ___________________________________________________________________
       (page generated 2021-05-20 23:03 UTC)