[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)