This is the 19th day of my participation in the August More Text Challenge

When we write multithreaded programs, some resources need to make sure that at the same time will not be multiple threads to access and manipulate, then typically use lock, concrete, the resources for accessing and manipulating the code snippet is called the critical region, through the lock lock, at the same time, there can be only one thread execute the critical area code, This ensures the mutual exclusion and atomicity of the critical section code.

In C++, mutex is used to lock critical sections. The basic definition of mutex is in the <mutex> header file.

classification

The <mutex> header provides two types of synchronization tools. One is a direct synchronization primitive, including:

  • Mutex: Exclusive, non-recursive mutex
  • Timed_mutex: Exclusive non-recursive mutex that supports timeout
  • Recursive_mutex: Exclusive, recursive mutex supported
  • Recursive_timed_mutex: Exclusive recursive mutex that supports timeout

The other is the lock packaging class, including:

  • Lock_guard: Implements a strictly scope-based mutex ownership wrapper
  • Unique_lock: Implements a removable mutex ownership wrapper
  • Scoped_lock: Deadlock-free RAII wrapper for multiple mutex

In general, wrap classes should be used more often than synchronization primitives. Wrap classes provide a better lock management mechanism to prevent deadlock and memory leaks caused by forgetting to call UNLOCK or making a wrong call to lock. This article begins with an introduction to these mutex locks.

Synchronization primitives

std::mutex

The mutex class is a synchronization primitive used to protect shared data accessed by multiple threads at the same time.

The mutex class has three member functions:

  • Lock: Takes ownership of the lock and blocks the calling thread if another thread already holds the lock
  • Try_lock: Attempts to acquire ownership of the lock, returning false if another thread already holds the lock
  • Unlock: Releases the ownership of the lock

When the calling thread calls the LOCK or try_lock method, it gains ownership of the mutex until it calls unlock. When one thread has acquired ownership of a mutex, another call to the lock method will block in the lock method. If another thread calls the try_lock method, it will return false indicating that another thread has acquired the lock.

std::timed_mutex

In addition to the lock, try_lock, and unlock methods of the mutex class, the timed_mutex class provides two methods to acquire lock ownership by timeout:

  • bool try_lock_for(const std::chrono::duration<Rep,Period>& timeout_duration)
  • bool try_lock_until(const std::chrono::time_point<Clock,Duration>& timeout_time)

False is returned after the attempt to acquire ownership of the lock has timed out

std::recursive_mutex

Recursive_mutex supports recursive mutexes. Once a thread has acquired a lock, it can continue to enter a critical section through try_lock or lock. In recursive_MUtex, a counter is used to count the number of times the lock was performed.

std::recursive_timed_mutex

Recursive_timed_mutex supports both timed_mutex and RECURsive_mutex.

The sample

Here are some examples of mutex uses.

#include <chrono>
#include <iostream>
#include <mutex>
#include <thread>

class Speaking {
 private:
  int a;
  std::mutex m;
  std::timed_mutex t_m;
  std::recursive_mutex r_m;
  std::recursive_timed_mutex r_t_m;

 public:
  Speaking() : a(0) {}; ~Speaking() = default;
  void speak_without_lock(a);
  void speak(a);
  void speak_timed_lock(a);
  void speak_recursive_lock(a);
  void speak_recursive_lock2(a);
  void speak_lock_without_recursive_lock(a);
  void speak_lock_without_recursive_lock2(a);
};

void Speaking::speak_without_lock(a) {
  std::cout << std::this_thread::get_id() < <":" << a << std::endl;
  a++;
}

void Speaking::speak(a) {
  m.lock(a);speak_without_lock(a); m.unlock(a); }void Speaking::speak_timed_lock(a) {
  if (t_m.try_lock_for(std::chrono::seconds(1))) {
    std::this_thread::sleep_for(std::chrono::seconds(2));
    speak_without_lock(a); t_m.unlock(a); }else {
    std::cout << std::this_thread::get_id() < <": time_out"<< std::endl; }}void Speaking::speak_recursive_lock(a) {
  r_m.lock(a);speak_recursive_lock2(a); r_m.unlock(a); }void Speaking::speak_recursive_lock2(a) {
  r_m.lock(a);speak_without_lock(a); r_m.unlock(a); }void Speaking::speak_lock_without_recursive_lock(a) {
  t_m.lock(a);speak_lock_without_recursive_lock2(a); t_m.unlock(a); }void Speaking::speak_lock_without_recursive_lock2(a) {
  if (t_m.try_lock_for(std::chrono::seconds(1))) {
    speak_without_lock(a); t_m.unlock(a); }else {
    std::cout << std::this_thread::get_id() < <": time_out"<< std::endl; }}int main(a) {
  Speaking s;

  // std::cout << "speak without lock:" << std::endl;

  std::thread t1(&Speaking::speak_without_lock, &s);
  std::thread t2(&Speaking::speak_without_lock, &s);
  t1.join(a); t2.join(a); std::cout <<"speak with lock:" << std::endl;
  std::thread t3(&Speaking::speak, &s);
  std::thread t4(&Speaking::speak, &s);
  t3.join(a); t4.join(a); std::cout <<"speak with timed lock:" << std::endl;
  std::thread t_t1(&Speaking::speak_timed_lock, &s);
  std::thread t_t2(&Speaking::speak_timed_lock, &s);
  t_t1.join(a); t_t2.join(a); std::cout <<"speak with recursive lock:" << std::endl;
  std::thread t_r1(&Speaking::speak_recursive_lock, &s);
  std::thread t_r2(&Speaking::speak_recursive_lock, &s);
  t_r1.join(a); t_r2.join(a); std::cout <<"speak without recursive lock:" << std::endl;
  std::thread t_r3(&Speaking::speak_lock_without_recursive_lock, &s);
  t_r3.join(a);return 0;
}
Copy the code

The running results are as follows:

  • When not using mutex for inter-thread synchronization, a++ is not atomic, and synchronization problems occur
  • Locking a++ with mutex does not cause interthread synchronization problems
  • With timed_mutex, since we set the critical section to sleep for 2s, false is automatically returned when another thread acquires a lock timeout of 1s
  • Timed_mutex does not support recursive_mutex. Timed_mutex does not support recursive_mutex. Timed_mutex does not support recursive_mutex It timed out on the second lock.

summary

This paper introduces the synchronization primitive mutex in c++, followed by a detailed description of the mutex wrapper class.