C++ concurrent programming (two): Mutex (mutual exclusion lock)

The original text is reproduced at: https://segmentfault.com/a/1190000006614695

Concurrent programming, thread video resources When
multiple threads access the same resource, in order to ensure data consistency, the easiest way is to use mutex (mutual exclusion lock).

Quoting the introduction of cppreference:

The mutex class is a synchronization primitive that can be used to
protect shared data from being simultaneously accessed by multiple
threads.

Mutex 1

Operate mutex directly, that is, directly call the lock / unlock function of mutex.

#include <iostream>
#include <mutex>
#include <thread>
#include <vector>


std::mutex g_mutex;
int g_count = 0;

void Counter() {
  g_mutex.lock();

  int i = ++g_count;
  std::cout << "count: " << i << std::endl;

  // 前面代码如有异常,unlock 就调不到了。
  g_mutex.unlock();
}

int main() {
  const std::size_t SIZE = 4;

  // 创建一组线程。
  std::vector<std::thread> v;
  v.reserve(SIZE);

  for (std::size_t i = 0; i < SIZE; ++i) {
    v.emplace_back(&Counter);
  }

  // 等待所有线程结束。
  for (std::thread& t : v) {
    t.join();
  }

  return 0;
}

Unfortunately, STL does not provide a tool such as boost::thread_group that represents a group of threads. Although std::vector can also achieve the goal, the code is not concise enough.

Mutex 2

Use lock_guard to automatically lock and unlock. The principle is RAII, similar to smart pointers.

#include <iostream>
#include <mutex>
#include <thread>
#include <vector>

std::mutex g_mutex;
int g_count = 0;

void Counter() {
  // lock_guard 在构造函数里加锁,在析构函数里解锁。
  std::lock_guard<std::mutex> lock(g_mutex);

  int i = ++g_count;
  std::cout << "count: " << i << std::endl;
}

int main() {
  const std::size_t SIZE = 4;

  std::vector<std::thread> v;
  v.reserve(SIZE);

  for (std::size_t i = 0; i < SIZE; ++i) {
    v.emplace_back(&Counter);
  }

  for (std::thread& t : v) {
    t.join();
  }

  return 0;
}

Mutex 3

Use unique_lock to automatically lock and unlock.
Unique_lock has the same principle as lock_guard, but provides more functions (for example, it can be used in conjunction with condition variables).
Note: mutex::scoped_lock is actually the typedef of unique_lock.

As for the detailed comparison of unique_lock and lock_guard, please refer to StackOverflow.

#include <iostream>
#include <mutex>
#include <thread>
#include <vector>

std::mutex g_mutex;
int g_count = 0;

void Counter() {
  std::unique_lock<std::mutex> lock(g_mutex);

  int i = ++g_count;
  std::cout << "count: " << i << std::endl;
}

int main() {
  const std::size_t SIZE = 4;

  std::vector<std::thread> v;
  v.reserve(SIZE);

  for (std::size_t i = 0; i < SIZE; ++i) {
    v.emplace_back(&Counter);
  }

  for (std::thread& t : v) {
    t.join();
  }

  return 0;
}

Mutex 4

Use a separate mutex for the output stream.
This is done because IO streams are not thread-safe!
If the IO is not synchronized, the output of this example is likely to become:

count == count == 2count == 41
count == 3

Because in the following output statement:

std::cout << "count == " << i << std::endl;

The two actions of output "count == "and i are not atomic and may be interrupted by other threads.

#include <iostream>
#include <mutex>
#include <thread>
#include <vector>

std::mutex g_mutex;
std::mutex g_io_mutex;
int g_count = 0;

void Counter() {
  int i;
  {
    std::unique_lock<std::mutex> lock(g_mutex);
    i = ++g_count;
  }

  {
    std::unique_lock<std::mutex> lock(g_io_mutex);
    std::cout << "count: " << i << std::endl;
  }
}

int main() {
  const std::size_t SIZE = 4;

  std::vector<std::thread> v;
  v.reserve(SIZE);

  for (std::size_t i = 0; i < SIZE; ++i) {
    v.emplace_back(&Counter);
  }

  for (std::thread& t : v) {
    t.join();
  }

  return 0;
}

Guess you like

Origin blog.csdn.net/qq_28581269/article/details/112980145