Detailed explanation of STD:: Thread Library (3)

Time:2021-4-17

catalog

  • catalog
  • preface
  • lock_guard
  • scoped_lock (C++17)
  • unique_lock
  • shared_lock
  • summary
  • ref

preface

The first two blogs introduced threads and locks in the standard library respectively. This blog will introduce lock management.

Lock is very common in multithreading programming, but if it is not used carefully, it will lead to many problems, the most common one is deadlock.

lock_guard

std::lock_guardLock management is the most common lock management class. It will automatically lock when it is initialized and unlock when it is destroyed. The objects that need to be locked meet the requirementsBasicLockableThat is to say, existencelockandunlockmethod. Test code:

void thread_func(int thread_id) {
  {
    std::lock_guard guard(global_mutex);
    std::cout << "Test 1:" << thread_id << std::endl;
    std::this_thread::sleep_for(1s);
    std::cout << "Test 2:" << thread_id << std::endl;
  }
  std::this_thread::sleep_for(0.5s);
  std::cout << "Test 3:" << thread_id << std::endl;
}

std::lock_guardThe code block is initialized at the beginning of the thread,global_mutexSo test 1 and test 2 will be output together, and then the code block ends,std::lock_guardAs a region variable, the lock is also released at this time. Its output is

lock_guard 输出

besides,std::lock_guardA second parameter is also allowedstd::adopt_lock_tThis parameter indicates that the thread has acquired the lock, so it does not need to acquire the lock when creating the object.

std::lock_guard lk(mutex1, std::adopt_lock);

scoped_lock (C++17)

This class andstd::lock_guardSimilar, but it can manage multiple locks at the same time and lock multiple locks at the same time. This method can be a good solutionThe dining problem of philosophers\(^1\)

void thread_func(int thread_id, std::mutex &mutex1, std::mutex &mutex2) {
  std::scoped_lock lock(mutex1, mutex2);
  std::cout << "Thread " << thread_id << " is eating." << std::endl;
  std::this_thread::sleep_for(1s);
  std::cout << "Thread " << thread_id << " over." << std::endl;
}

std::vector> philosopher;
std::vector tableware_mutex(5);
for (int loop_i = 0; loop_i < 5; ++loop_i) {
  philosopher.push_back(
    std::make_shared(thread_func, loop_i, std::ref(tableware_mutex[loop_i]), std::ref(tableware_mutex[(loop_i + 1) % 5]))
  );
}

for (int loop_i = 0; loop_i < 5; ++loop_i) {
  philosopher.at(loop_i)->join();
}

Here we initialize five philosophers (threads) and five utensils (locks). Each philosopher needs two adjacent utensils to eat. The result is simple

scoped_lock 输出

As you can see, these philosophers eat in an orderly way without conflict. And if we take the correspondingstd::scoped_lock lock(mutex1, mutex2);, to twostd::lock_guardThe naked eye will appear disgusting deadlock problem.

andstd::lock_guardSimilarly, it has a parameterstd::adopt_lock_t, which indicates that the thread has acquired the lock, and it does not need to acquire the lock during construction, but this parameter is in the first.

std::scoped_lock lk(std::adopt_lock, mutex1, mutex2);

unique_lock

std::unique_lockCompared withstd::lock_guardMore freedom, exceptstd::adopt_lock_tIn addition to parameters, it also supportstry_to_lock_tdefer_lock_tOf whichtry_to_lock_tIt is a non blocking lock,defer_lock_tDo not lock during initialization.

std::unique_lock lk(mutex, std::adopt_lock);
std::unique_lock lk(mutex, std::try_to_lock);
std::unique_lock lk(mutex, std::defer_lock);

std::unique_lockSupport timeout locking:

std::unique_lock lk(mutex, 1s);

std::unique_lockIt supports mobile semantics, so it can be used as a return value

std::unique_lock get_lock() {
  std::unique_lock lk(mutex);
  return lk;
}

void thread_func(int thread_id) {
  std::unique_lock lk = get_lock();
  std::cout << "Test 1: " << thread_id << std::endl;
  std::this_thread::sleep_for(1s);
  std::cout << "Test 2: " << thread_id << std::endl;
}

Since it allows construction without locking, it also provides the correspondinglocktry_lockunlockAnd so on.

shared_lock

andstd::unique_lockSimilar, but this is to lock the read part of the read-write lock.

summary

This paper summarizes all the lock management classes in the standard library. Reasonable use can make the code more beautiful. This is the third blog post of standard library thread, and the fourth will introduce the conditional variables in thread.

ref

[1] https://zh.wikipedia.org/wiki/ The dining problem of philosophers

Blog text: https://www.cnblogs.com/ink19/p/std_ thread-3.html

Recommended Today

Review of SQL Sever basic command

catalogue preface Installation of virtual machine Commands and operations Basic command syntax Case sensitive SQL keyword and function name Column and Index Names alias Too long to see? Space Database connection Connection of SSMS Connection of command line Database operation establish delete constraint integrity constraint Common constraints NOT NULL UNIQUE PRIMARY KEY FOREIGN KEY DEFAULT […]