C++中的锁🔒

C++中的互斥锁

C++14中有五种互斥锁:

  • std::mutex
  • std::timed_mutex
  • std::recursive_mutex
  • std::recursive_timed_mutex
  • std::shared_timed_mutex

其中,名称中带有“timed”的变体和不带“timed”的变体相同,只是锁定操作可以指定超时,以限制最大等待时间。如果没有指定超时,那么锁操作将阻塞,直到可以获取所为止–如果持有锁🔒的线程从不释放它,则可能永远阻塞。

std::mutexstd::timed_mutex只是普通的单所有者互斥锁。 std::recursive_mutexstd::recursive_timed_mutex是递归互斥锁,因此单个线程可以持有多个锁。 std::shared_timed_mutex是一个读/写互斥锁。

C++锁定对象

为了配合各种互斥锁类型,C++标准为持有锁的对象定义了三组类模板。分别为:

  • std::lock_guard<>
  • std::unique_lock<>
  • std::shared_lock<> 对于基本操作,它们都在构造函数中获取锁,并在析构函数中释放。但是如果出于需要,它们可以以更复杂的方式使用。

std::lock_guard<>是最简单的类型,只在单个块中的关键部分持有锁:

1
2
3
4
5
std::mutex latch_;
void f() {
  std::lock_guard<std::mutex> guard(latch_);
  // do stuff...
}

std::unique_lock<>类似,只不过它可以从函数返回而不释放锁,并且可以在析构函数之前释放锁:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
std::mutex rwlatch_;
std::unique_lock<std::mutex> f() {
  std::unique_lock<std::mutex> guard(rwlatch_);
  // do stuff...
  return std::move(guard);
}

void g() {
  std::unique_lock<std::mutex> guard(f());
  // do more stuff
  guard.unlock();
}

std::shared_lock<>std::unique_lock<>几乎一样,只是它需要mutex上的共享锁。

如果使用std::shared_timed_mutex作为mutex,那么可以用std::lock_guard<std::shared_timed_mutex>或者std::unique_lock<std::shared_timed_mutex>获得排它锁(X),使用std::shared_lock<std::shared_timed_mutex>获取共享锁(S)。

1
2
3
4
5
6
7
8
9
std::shared_timed_mutex latch_;
void reader() {
  std::shared_lock<std::shared_timed_mutex> guard(latch_);
  // do read-only stuff, S lock here, read-only
}
void writer() {
  std::lock_guard<std::shared_timed_mutex> guard(latch_);
  // update shared data, acquire the X lock, so update value
}

C++中的信号量Semaphores

C++标准中并没有定义信号量类型。因此我们可以根据自己的需要使用原子计数器,互斥锁,或者条件变量实现我们自己的信号量。但无论如何,大多数情况下信号量的使用可以用互斥锁/条件变量替换。

在某些情况下,如果确实需要使用信号量,使用mutex和conditional variable会增加开销,C++标准中没有解决方案。