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


No comments: