Saturday, January 20, 2024

The Barrier in C++ 20 concurrency - the programmer in me is still thriving...

Enjoy my training video on the C++ barrier...



Suppose three workers prepare data at different speeds, but a computation must begin only after all workers are ready. How do we synchronize them?

std::barrier is the answer in C++.

The std::barrier class is a synchronization primitive introduced in C++20. It allows a set of threads to synchronize at a certain point in their execution. It is similar to the std::latch class, but it can be reused multiple times.

A std::barrier object is initialized with a count, which specifies the number of threads that must reach the barrier before any of them can proceed. When a thread reaches the barrier, it calls the wait() method. If the count is not yet zero, the thread will be blocked until the count reaches zero. Once the count reaches zero, all of the threads that are waiting on the barrier will be released and can proceed.

The std::barrier class can be used to implement a variety of synchronization patterns, such as producer-consumer queues, parallel algorithms, and race condition prevention.

Here's my application in which I used barrier to showcase how it can be used.

Class Student

#include <iostream>

#include <string>

#include <thread>

#include <barrier>

#include <chrono>

#include <vector>

#include <syncstream> // For thread-safe console output

#include <format> // For C++20 string formatting


class Student {

private:

std::string name;

int timeLapseBeforeStarting; // in milliseconds

int timeToFinish; // in milliseconds


public:

// Optimized constructor using std::move to avoid redundant copies

Student(std::string studentName, int lapse, int finish)

: name(std::move(studentName)),

timeLapseBeforeStarting(lapse),

timeToFinish(finish) {}


// Virtual destructor is fine if subclassing, but defaulted here

virtual ~Student() = default;


void task(std::barrier<>& b) {

// 1. Simulate varying arrival times

std::this_thread::sleep_for(std::chrono::milliseconds(timeLapseBeforeStarting));


// 2. Synchronize at the barrier

b.arrive_and_wait();


auto now = std::chrono::system_clock::now();


// Locate the current system timezone and convert UTC to local time

auto local_time = std::chrono::current_zone()->to_local(now);


// std::osyncstream guarantees this block of output won't interleave with other threads

std::osyncstream(std::cout)

<< name << " is Starting the task at "

<< std::format("{:%F %T}", local_time) << "\n";

// 4. Simulate performing the task

std::this_thread::sleep_for(std::chrono::milliseconds(timeToFinish));


std::osyncstream(std::cout) << name << " finished the task.\n";

}

};

// ==========================================

// Class classETC

// ==========================================

#include "Student.h"

class classETC {

public:

Student ridit {"Ridit", 1000, 2000};

Student ishan {"Ishan", 3000, 1000};

Student rajdeep {"Rajdeep", 900, 1500};


classETC() = default;

virtual ~classETC() = default;


// FIX: Returns a container of jthreads to main() to keep them alive

auto giveTaskToStudent(std::barrier<>& b) {

std::vector<std::jthread> workers;

workers.reserve(3);


// Emplace threads directly into the vector

workers.emplace_back(&Student::task, &this->ridit, std::ref(b));

workers.emplace_back(&Student::task, &this->ishan, std::ref(b));

workers.emplace_back(&Student::task, &this->rajdeep, std::ref(b));


return workers; // Named Return Value Optimization (NRVO) handles this cleanly

}

};

The Main method

// ==========================================

// Main Method

// ==========================================

#include "classETC.h"

int main() {

// Print C++ standard validation

std::cout << "C++ Version: ";

if (__cplusplus == 202101L) std::cout << "C++23\n";

else if (__cplusplus == 202002L) std::cout << "C++20\n";

else std::cout << "Other/Experimental (" << __cplusplus << ")\n";

std::cout << "-------------------------------------------\n";


std::barrier b(3);

classETC etc;


// FIX: Capturing the threads in main() preserves their lifetimes.

// They will execute concurrently and automatically join when main() ends.

auto threads = etc.giveTaskToStudent(b);


return 0;

}

The heart of the above concurrent program is

 b.arrive_and_wait();
  • arrive() decrements the participant count.

  • wait() blocks until the count reaches zero.

  • arrive_and_wait() combines both.

The Output:

C++ Version: C++20

-------------------------------------------

Ridit is Starting the task at 2026-06-26 00:19:59.803930207

Ishan is Starting the task at 2026-06-26 00:19:59.803930524

Rajdeep is Starting the task at 2026-06-26 00:19:59.803929510

Ishan finished the task.

Rajdeep finished the task.

Ridit finished the task.

Have a look at the time when the three different threads are starting - all of them start at the same time - 00:19:59 - why?

It's because of the barrier!!!

And for the inquisitive mind...

Why is there a mismatch in the starting time in the range of just a few microseconds?

A variation of 1 to 20 microseconds is incredibly tight and proves that our std::barrier system is doing its job perfectly. In a multi-threaded system running on top of a general-purpose operating system (like Linux/Ubuntu), achieving true 0.000000 synchronicity is physically impossible due to these hardware and scheduling realities. To get any closer, we would need a hard Real-Time Operating System (RTOS) with core pinning and disabled interrupts!

Enjoy...

Friday, January 12, 2024

National Youth Day - here's is a clarion call to the engineers of Bharat - wake up and embrace Sanskrit...

 


Nikola Tesla and Swami Vivekananda

Mr. Toby Grotz, President, Wireless Engineering

Swami Vivekananda, late in the year l895 wrote in a letter to an English friend, "Mr. Tesla thinks he can demonstrate mathematically that force and matter are reducible to potential energy. I am to go and see him next week to get this new mathematical demonstration. In that case, the Vedantic cosmology will be placed on the surest of foundations. I am working a good deal now on the cosmology and eschatology of the Vedanta. I clearly see their perfect union with modern science, and the elucidation of the one will be followed by that of the other." (Complete Works, Vol. V, Fifth Edition, 1347, p. 77).

Here Swamiji uses the terms force and matter for the Sanskrit terms Prana and Akasha. Tesla used the Sanskrit terms and apparently understood them as energy and mass. (In Swamiji's day, as in many dictionaries published in the first half of the present century, force and energy were not always clearly differentiated. Energy is a more proper translation of the Sanskrit term Prana.)

Tesla apparently failed in his effort to show the identity of mass and energy. Apparently, he understood that when speed increases, mass must decrease. He seems to have thought that mass might be "converted" to energy and vice versa, rather than that they were identical in some way, as is pointed out in Einstein's equations. At any rate, Swamiji seems to have sensed where the difficulty lay in joining the maps of European science and Advaita Vedanta and set Tesla to solve the problem. It is apparently in the hope that Tesla would succeed in this that Swamiji says "In that case, the Vedantic cosmology will be placed on the surest of foundations."

Unfortunately, Tesla failed and the solution did not come till ten years later, in a paper by Albert Einstein. But by then Swamiji was gone and the connecting of the maps was delayed.

Engineers of Bharat - don't spend your life as a wage slave.



Reclaim your true identity... 

Embrace #Sanskrit

Read... Read...



Here's why we must declare Sanskrit as the national language of Bharat...


Let's discard Unity in diversity and embrace Unity in Unison - let's all embrace Sanskrit...




Here's my wife Reema reciting her own poem on wisdom


Sunday, January 7, 2024

Latches in C++ 20 concurrency - just like the CountdownLatch of Java concurrency package...

Multithreaded programming is inherently difficult. One of the reasons is that we can't have control over how a thread will start and finish - in which order - it all depends upon the thread scheduling algorithm of the OS. This makes the reproduction of test cases difficult. Moreover, there are race conditions and deadlocks.

When I was teaching the Countdown latch - a thread synchronization technique used in the Java Concurrency package, there was none like that available in C++. I  am happy to see that the concept of latch is introduced in C++20.

So...

What is a Latch in C++?

  • A synchronization primitive was introduced in C++20.
  • It allows one or more threads to wait for a certain number of operations to complete before proceeding.
  • Acts like a countdown counter that blocks threads until it reaches zero.
Good to know that the C++ team is trying to catch up with Java...

Here we go...

My experimentation with C++ latches.




I taught my young son Ridit about the Java Countdown latch three years ago.

Good to see the C++ team is making the standard library more powerful day by day.

Here is the C++ source code of my experimentation.

The following C++ code needs C++20 to compile and execute.


/*

* Student.h

*

* Created on: Jan 7, 2024

* Author: som

*/


#ifndef STUDENT_H_

#define STUDENT_H_


#include <iostream>

#include <string>

#include <thread>

#include <latch>

#include <chrono>

#include <vector>

#include <syncstream> // For thread-safe console output

#include <format> // For C++20 string formatting



class Student {


private:

std::string name;

int timeToFinish;


public:

Student(std::string name, int finish)

: name(std::move(name)),

timeToFinish(finish) {}


virtual ~Student() = default;


void task(std::latch& l){


auto now = std::chrono::system_clock::now();


// Locate the current system timezone and convert UTC to local time

auto local_time = std::chrono::current_zone()->to_local(now);


std::osyncstream(std::cout)

<< name << " is Starting the task at "

<< std::format("{:%F %T}", local_time) << "\n";

// 4. Simulate performing the task

                std::this_thread::sleep_for(std::chrono::milliseconds(timeToFinish));


std::osyncstream(std::cout) << name << " finished the task.\n";


l.count_down();

}

};


#endif /* STUDENT_H_ */




/*

* classETC.h

*

* Created on: Jan 7, 2024

* Author: som

*/


#ifndef CLASSETC_H_

#define CLASSETC_H_


#include "Student.h"



class classETC {


public:

Student ridit {"Ridit", 1000};

Student ishan {"Ishan", 3000};

Student rajdeep {"Rajdeep", 900};


classETC() = default;


virtual ~classETC() = default;



auto giveTaskToStudent(std::latch& l){


std::vector<std::jthread> workers;

workers.reserve(3);


// Emplace threads directly into the vector

workers.emplace_back(&Student::task, &this->ridit, std::ref(l));

workers.emplace_back(&Student::task, &this->ishan, std::ref(l));

workers.emplace_back(&Student::task, &this->rajdeep, std::ref(l));


std::cout<<"Teacher is waiting for all the students to finish their task"<<std::endl;


l.wait();


std::cout<<"All students submitted their task... Teacher is leaving the class"<<std::endl;


return workers; // Named Return Value Optimization (NRVO) handles this cleanly


}

};


#endif /* CLASSETC_H_ */




Main:


// ==========================================

// Main Method

// ==========================================

#include "classETC.h"

int main() {

// Print C++ standard validation

std::cout << "C++ Version: ";

if (__cplusplus == 202101L) std::cout << "C++23\n";

else if (__cplusplus == 202002L) std::cout << "C++20\n";

else std::cout << "Other/Experimental (" << __cplusplus << ")\n";

std::cout << "-------------------------------------------\n";


std::latch l(3);

classETC etc;


// FIX: Capturing the threads in main() preserves their lifetimes.

// They will execute concurrently and automatically join when main() ends.

auto threads = etc.giveTaskToStudent(l);


return 0;

}


Explanation of the code:


Have a look at the Student class. Each student will do their work in a
background thread. As there are 3 students, the latch will be initiated
with 3.

After completing the task, a student will reduce the latch counter by 1.
So when all the students complete the task, the latch - on which
the main thread was blocked, will become zero, and it will unblock the
main thread.

The C++ latches work exactly the way the Java Countdown Latch
works.


For your reference, here's my son Ridit on Java CountdownLatch.


#Enjoy