Sunday, August 9, 2026

If this page is useful, please consider donating a coffee

Saturday, August 8, 2026

Understanding std::counting_semaphore and std::binary_semaphore from C++20This article explains the two semaphore types introduced in C++20: std::counting_semaphore and std::binary_semaphore . We’ll first use a counting semaphore to limit how many threads can operate at the same time. Then we’ll use a binary semaphore to send a signal between threads. We’ll also look at timed waiting, a small RAII helper, and a few more details. Note: The synchronization features discussed here are available in C++20. The examples use C++23 std::println for cleaner output. Let’s go. Basics A mutex works well when only one thread should enter a protected section at a time. But sometimes that limit is too strict. Imagine an application with three database connections. Running only one database operation at a time would waste two of them. On the other hand, allowing any number of threads to start an operation could overload the database. What we need is a limit: three threads may continue, while the others wait. There is another common case. One thread prepares some data, and another thread waits until the data is ready. Semaphores work well for both problems. A lot of multi-threading libraries have semaphores, but it’s pretty cool that the C++20 Standard Library now includes them right out of the box. API A counting semaphore is declared as: std::counting_semaphore The main operations are: Function Description counting_semaphore(desired) Creates a semaphore with the counter set to desired acquire() Decreases the counter, or waits if it is zero release(update) Increases the counter by update ; the default is 1 try_acquire() Tries once without waiting try_acquire_for() Waits for a limited duration try_acquire_until() Waits until a given time point max() Returns the largest counter value supported by the implementation std::binary_semaphore is an alias for: std::counting_semaphore It is useful when one outstanding signal is enough. Let’s start with the counting version. Limiting concurrency with std::counting_semaphore Suppose we have eight jobs, but only three should perform an expensive operation at the same time: #include #include #include #include // #include #include int main () { constexpr int workerCount = 8 ; constexpr int slotCount = 3 ; std :: counting_semaphore slotCount > slots { slotCount }; std :: mutex outputMutex ; int active = 0 ; auto worker = [ & ]( int id ) { slots . acquire (); { std :: lock_guard lock ( outputMutex ); ++ active ; std :: println ( "worker {} entered; active={}" , id , active ); } std :: this_thread :: sleep_for ( std :: chrono :: milliseconds ( 250 ) ); { std :: lock_guard lock ( outputMutex ); -- active ; std :: println ( "worker {} leaving; active={}" , id , active ); } slots . release (); }; std :: vector std :: jthread > threads ; threads . reserve ( workerCount ); for ( int i = 0 ; i workerCount ; ++ i ) threads . emplace_back ( worker , i ); } Run @Compiler Explorer The semaphore starts with three available slots. The first three workers call acquire() and continue. The internal counter then reaches zero. When a fourth worker calls acquire() , it has to wait. Once one of the active workers finishes and calls release() , a slot becomes available again. One waiting worker can then continue. One possible part of the output is: worker 0 entered; active=1 worker 2 entered; active=2 worker 1 entered; active=3 worker 2 leaving; active=2 worker 4 entered; active=3 The order may change between runs, but active should never be greater than three. The mutex in this example does not limit the number of workers. It only protects the diagnostic counter and keeps the output readable. The semaphore is the part that enforces the three-worker limit. Logging can slightly change thread scheduling, but it does not change the rule enforced by the semaphore. The mutex is held only for a short print operation, not for the simulated work. This is also why the code is not a traditional critical section. Three workers are allowed to run the operation together. We are limiting concurrency, not forcing complete mutual exclusion. There is one more detail: the semaphore knows how many slots are available, but it does not know what those slots represent. If they stood for three real database connections, we would still need a separate container holding those connections. Returning a slot with RAII The example above calls release() by hand. That works, but it is easy to make a mistake. An early return or an exception between acquire() and release() could prevent the slot from being returned. Other threads might then wait forever, even though the underlying work has already stopped. This is similar to calling lock() and forgetting to call unlock() . A small RAII guard can help: template std :: ptrdiff_t LeastMaxValue > class SemaphoreGuard { public : explicit SemaphoreGuard ( std :: counting_semaphore LeastMaxValue >& sem ) : sem_ ( sem ) { sem_ . acquire (); } ~ SemaphoreGuard () { sem_ . release (); } SemaphoreGuard ( const SemaphoreGuard & ) = delete ; SemaphoreGuard & operator = ( const SemaphoreGuard & ) = delete ; private : std :: counting_semaphore LeastMaxValue >& sem_ ; }; It can be used at the start of a scope: SemaphoreGuard guard { slots }; The constructor waits for a slot, and the destructor returns it. This works well when the slot represents reusable capacity: a database connection, an upload slot, a buffer, or access to limited hardware. On the other hand, it does not fit every signaling example. A one-way signal is often meant to be consumed rather than returned. Signaling with std::binary_semaphore A mutex has ownership. The thread that locks it must also unlock it. A semaphore does not have this rule. One thread can wait in acquire() , while another thread calls release() . That makes std::binary_semaphore useful for simple communication between threads. In the next example, the worker waits until the main thread tells it to start. It calculates a result and then sends a signal back: #include #include #include int main () { std :: binary_semaphore startSignal { 0 }; std :: binary_semaphore doneSignal { 0 }; int result = 0 ; std :: jthread worker ([ & ] { startSignal . acquire (); result = 42 ; doneSignal . release (); }); std :: println ( "[main] starting worker" ); startSignal . release (); doneSignal . acquire (); std :: println ( "[main] result = {}" , result ); } Run @Compiler Explorer Both semaphore counters start at zero. The worker stops at startSignal.acquire() . The main thread calls startSignal.release() , which sends the start signal and lets the worker continue. After writing result , the worker calls doneSignal.release() . The main thread waits for this signal before reading the result. The semaphore also handles memory synchronization. The main thread sees the writes made by the worker before the release() that allowed doneSignal.acquire() to finish. That means the main thread can safely read result after doneSignal.acquire() returns. The signal is not returned afterward, and that is fine. It represented a one-time notification rather than a reusable slot. Waiting with a timeout acquire() can wait forever. Some code cannot accept that. For example, an application may want to report an error, retry the operation, or switch to a fallback path after waiting for too long. try_acquire_for() waits for a relative duration: #include #include #include #include int main () { std :: counting_semaphore 1 > sem { 0 }; std :: jthread notifier ([ & ] { std :: this_thread :: sleep_for ( std :: chrono :: milliseconds ( 200 ) ); sem . release (); }); if ( sem . try_acquire_for ( std :: chrono :: milliseconds ( 100 ))) { std :: println ( "First wait succeeded" ); } else { std :: println ( "First wait timed out" ); } if ( sem . try_acquire_for ( std :: chrono :: milliseconds ( 300 ))) { std :: println ( "Second wait succeeded" ); } else { std :: println ( "Second wait timed out" ); } } See @Compiler Explorer A typical result is: First wait timed out Second wait succeeded The first call stops waiting before the notifier calls release() . It does not decrease the counter. Later, the notifier increases the counter, and the second call succeeds. A timeout is not an exact scheduling deadline. Waiting for 100 milliseconds means the function will not report a timeout before that duration has passed. The operating system may resume the thread a little later. Semaphore, mutex, or condition variable? These tools solve different problems. Use a mutex when one thread should own a protected section. The thread that locks the mutex must unlock it, and std::lock_guard makes that ownership easy to manage. Use a counting semaphore when up to N operations may run at the same time. Use a binary semaphore when one thread needs to send a simple signal to another thread. A condition variable is a better fit when threads wait for shared state to satisfy a condition. It normally works together with a mutex and a predicate. Tool Common use std::mutex One thread owns a protected section std::counting_semaphore Up to N operations run together std::binary_semaphore One thread signals another std::condition_variable Threads wait for a shared-state condition A semaphore also remembers an unused counter value. If release() runs before another thread starts waiting, a later acquire() can still use that value. With a condition variable, code normally checks shared state rather than relying on a stored notification. Details worth knowing LeastMaxValue is a lower bound In: std :: counting_semaphore 3 > the value 3 means that the implementation must support a counter of at least three. Its real maximum may be larger. max() returns the value supported by the implementation. Application code should normally use the number it actually needs and not depend on any extra range offered by one standard library. Do not increase the counter past max() release(update) requires that the new counter value does not exceed max() . This matters with std::binary_semaphore . It is not an event flag that can be set many times without being cleared. Calling release() twice without an acquire() between those calls may break the function’s precondition. try_acquire() may fail spuriously try_acquire() performs one non-blocking attempt. It is allowed to return false even when the counter is greater than zero. Use acquire() when the thread must wait. Use try_acquire_for() or try_acquire_until() when it should wait only for a limited time. Waiting order is not guaranteed If several threads are waiting, the standard does not say that the oldest waiter must run first. Code that needs strict first-in, first-out behavior requires an extra queue or scheduling layer. Watch the lifetime Do not destroy a semaphore while another thread may still be using it. All calls to acquire() , release() , and the timed functions must finish before the semaphore’s lifetime ends. Summary std::counting_semaphore is useful when several threads may run an operation together, but their number must stay below a fixed limit. std::binary_semaphore works well for simple signals between threads. Use a mutex for normal exclusive access and a condition variable when threads wait for a shared-state condition. The main thing is to be clear about what the counter means. It may represent free worker slots, connections, buffers, or a signal waiting to be received. References std::counting_semaphore and std::binary_semaphore - cppreference C++ working draft: semaphores Implementing C++20 semaphores - Red Hat Developer C++ Concurrency in Action, Second Edition by Anthony Williams Concurrency with Modern C++ by Rainer Grimm Back to you Do you use semaphores mainly for limiting concurrent work or for signaling? Have you tried C++20 or some third-party libraries?πŸ“C++ Stories

Friday, August 7, 2026

Thursday, August 6, 2026

Kitware Awarded up to $17.5 Million ARPA-H Contract to Develop Virtual Testing Platform for Robot-Assisted Stroke Interventions3.5-year TAIR project will create a realistic, patient-specific virtual environment to accelerate the development and evaluation of robotic stroke devices. Clifton Park, NY β€” August 6, 2026 β€” Kitware has been awarded up to $17.5 million by the Advanced Research Projects Agency for Health (ARPA-H), an agency within the U.S. Department of Health and Human […]πŸ“Kitware Inc
Measurement uncertainty and measured constantsMeasurement uncertainty and measured constants When you write constexpr double G = 6.674e-11; in your code, the type system makes a claim that is not true. It claims the value is exact. It is not. The Newtonian constant of gravitation is one of the least precisely known constants in physics. CODATA 2018 lists it as 6.674 30(15) Γ— 10⁻¹¹ mΒ³ kg⁻¹ s⁻² , and the (15) means the fifth significant digit is already uncertain. Only four digits of G are actually known. Every result derived from that double inherits an uncertainty that the program neither tracks nor reports. The code happily prints ten significant digits of a solar mass computed from a constant that guarantees four. mp-units now models this honestly. This post introduces the uncertain representation type, the standard_uncertainty and relative_standard_uncertainty metadata for measured constants, and the measurement_of helper that connects them.πŸ“mp-units

Wednesday, August 5, 2026

ColourfieldsOver on the (inexplicably login-walled) site "NewEnigma," Keith Austin [writes](https://www.newenigma.com/enigma/view/1277/Colourfields): > Draw a 4-by-4 grid. Colour each square red or blue. Select any square, S, and write 1 in it. > Then write 1 in every square you can reach from S by a series of moves, where each move is from > a square to an adjacent, horizontally, vertically, or diagonally, square of the same colour [...] In brief: What is the maximum number of _queenwise-connected_ monochromatic regions you can make by coloring each square of an $$n\times n$$ grid either red or blue?πŸ“Arthur O’Dwyer
New bazel.build websites incoming!We're happy to announce the launch of the new bazel.build documentation site and the new web UI for the Bazel Central Registry! New documentation site Last year, Alan Mond wrote a viral blog post that highlighted the issues with Bazel's documentation site. This led to his Let's Build the Future of Bazel Documentation birds-of-a-feather session at BazelCon 2025, where we decided to overhaul the documentation completely. Alan and a group of volunteers from the community and Google have now completed the first and most significant milestone by migrating https://bazel.build from a proprietary Google hosting site to Mintlify . This step improves the experience for contributors and readers alike: It's easier to make (significant) changes to the docs since we now use the well-documented .mdx format. Moreover, contributors can now generate a preview of their changes either locally or by using the new automated PR preview feature ( example ). For readers, Mintlify offers AI features such as an embedded assistant that allows them to prompt the docs directly. Documentation content continues to live in the bazelbuild/bazel GitHub repository next to the Bazel source code. The new bazel-contrib/bazel-docs repository contains the documentation pipeline and the navigation. There are still some rough edges that we plan to address in the near future. Known issues are being tracked here - please post a comment when you encounter a bug. We hope that the new website makes it easier for external developers to contribute to the documentation, especially when it comes to significant changes like revamping the structure or adding new pages such as codelabs and tutorials. Consequently, we welcome anyone to contribute to these efforts - please check https://bazel.build/contribute/docs for instructions, or visit us in the #documentation Slack channel . New BCR UI Community member Paul Johnston has built an entirely new web UI for the Bazel Central Registry at https://registry.bazel.build/ . The new site brings several improvements over the previous one: Detailed overview : The new site features a denser display of key information about the module, including its dependencies, maintainers, and other versions. It also puts the prose documentation of the module front and center, pulled directly from the README.md file of the corresponding GitHub repo. Smarter information retrieval : Beside pulling README files, the new site has quite a few new tricks up its sleeve. It tries to surface official API documentation from the module, and if it's missing, it runs Stardoc on the .bzl files automatically and surfaces the result. It also presents the attestations and presubmit configs associated with each module version. Powerful search and indexes : Beyond searching for modules by their names, you can also search by symbols - try searching for go_library . You can also view the list of available modules filtered by their primary language or maintainers. Faster UI update times : Module authors should expect quicker updates to the BCR UI. When new commits land in the BCR GitHub repo, a repository dispatch triggers a new BCR UI build that takes on average less than 5 minutes. The new static assets are deployed to GitHub Pages in another 1-2 minutes. Other tidbits : The new site has many other corners to explore. One of them is the Bazel flags page , which lists all the command-line flags present in the past few Bazel LTS releases. For each individual flag, it also tells you which Bazel versions it is present in, and which Bazel subcommands it applies to. Please take it for a drive, and file issues and send PRs to https://github.com/bazel-contrib/bcr-frontend ! Finally, a huge thank you again to all community members who contributed to these efforts, especially Kayce Basques, Alex Eagle, Paul Johnston, Alan Mond, Armando Montanez, Anthony Pratti, Nikki Vijaybhaskar and Kapunahele Wong - they wouldn't have been possible without you!πŸ“Bazel Blog
Say hello to the new bazel.build websiteSay hello to the new bazel.build website Today, the new https://bazel.build/ website has finally launched. This is thefirst step in a larger evolution of Bazel's documentation story. Much ofthe content remains the same (the eagle-eyed among you may notice a few newpages), but this change marks a new era of Bazel documentation. What motivated this change? The Bazel site migration is more than just a fresh coat of paint. One of theunderlying motivations for this change is to empower the Bazel community to makemore changes to Bazel's documentation. Before the migration, Bazel's website wasbuilt on Google documentation infrastructure. While the capabilities of thatinfrastructure were largely sufficient for Bazel's needs up to now, thecontribution experience was not. Today's https://bazel.build/ is built on Mintlify .With this new platform, each incoming documentation pull request to Bazel isautomatically greeted with a preview. For even faster iteration, you can build the sitelocally . Theseimprovements eliminate guesswork, and better equip contributors to make changesto the Bazel docs with confidence.πŸ“EngFlow Blog

Tuesday, August 4, 2026

Monday, August 3, 2026