互斥锁、自旋锁、条件变量
互斥锁使用std::mutex
类;条件变量使用std::condition_variable
类;自旋锁通过C++11的std::atomic
类实现,使用“自旋”的CAS操作。
自旋锁参考:C++11实现自旋锁
#include <thread>
#include <mutex>
#include <iostream>
#include <atomic>
#include <condition_variable>
using namespace std;
// 使用C++11的原子操作实现自旋锁(默认内存序,memory_order_seq_cst)
class spin_mutex {
// flag对象所封装的bool值为false时,说明自旋锁未被线程占有。
std::atomic<bool> flag = ATOMIC_VAR_INIT(false);
public:
spin_mutex() = default;
spin_mutex(const spin_mutex&) = delete;
spin_mutex& operator= (const spin_mutex&) = delete;
void lock() {
bool expected = false;
// CAS原子操作。判断flag对象封装的bool值是否为期望值(false),若为bool值为false,与期望值相等,说明自旋锁空闲。
// 此时,flag对象封装的bool值写入true,CAS操作成功,结束循环,即上锁成功。
// 若bool值为为true,与期望值不相等,说明自旋锁被其它线程占据,即CAS操作不成功。然后,由于while循环一直重试,直到CAS操作成功为止。
while(!flag.compare_exchange_strong(expected, true)){
expected = false;
}
}
void unlock() {
flag.store(false);
}
};
int k = 2;
// 实现互斥
spin_mutex smtx; // 自旋锁,如果自旋锁已经被占用,调用者就一直循环检查自旋锁是否被解除占用。
mutex mtx; // 互斥锁,如果互斥锁已经被占用,调用者这会进入睡眠状态,等待互斥锁解除占用时被唤醒。
// 实现同步
condition_variable cond;
// 不加锁
void print_without_mutex(int n){
for(int i = 1; i <= n ; i++){
cout << this_thread::get_id() << ": " << i << endl;
}
}
// 使用互斥锁,实现互斥
void print_with_mutex(int n){
unique_lock<mutex> lock(mtx);
for(int i = 1; i <= n ; i++){
cout << this_thread::get_id() << ": " << i << endl;
}
}
// 使用自旋锁,实现互斥
void print_with_spin_mutex(int n){
smtx.lock();
for(int i = 1; i <= n ; i++){
cout << this_thread::get_id() << ": " << i << endl;
}
smtx.unlock();
}
// 使用条件变量,实现同步(需要与mutex配合使用),打印 1,1,2,2,3,3...
void print_with_condition_variable(int n){
unique_lock<mutex> lock(mtx);
for(int i = 1; i <= n ; i++){
while(!(i <= k/2)){ // 循环检查某个条件(i <= k/2)是否满足,满足则跳出循环,继续向下执行。
cond.wait(lock); // 阻塞当前线程,等待lock对象封装的互斥锁mtx解除占用(收到解除占用互斥锁的线程的notify)
}
cout << this_thread::get_id() << ": " << i << endl;
k++;
cond.notify_one(); // 随机唤醒一个等待的线程
}
}
int main(){
thread t1(print_with_spin_mutex,10);
thread t2(print_with_spin_mutex,10);
t1.join();
t2.join();
return 0;
}
读写锁
C++17提供了shared_mutex
来解决读者-写者问题,也就是读写锁。和普通锁不一样,读写锁同时只能有一个写者或多个读者,但不能同时既有读者又有写者,读写锁的性能一般比普通锁要好。
shared_mutex
比一般的 mutex
多了函数 lock_shared() / unlock_shared()
,允许多个(读者)线程同时加锁、解锁,而 shared_lock
则相当于共享版的 lock_guard
。对 shared_mutex
使用 lock_guard
或 unique_lock
就达到了写者独占的目的。
一下代码源自:C++ 并发编程(七):读写锁(Read-Write Lock)
#include <thread>
#include <shared_mutex>
#include <iostream>
#include <vector>
#include <unistd.h> // sleep(seconds), usleep(microseconds)
using namespace std;
class Counter {
public:
Counter() : value_(0) {
}
// Multiple threads/readers can read the counter's value at the same time.
std::size_t Get() const {
std::shared_lock<std::shared_mutex> lock(mutex_);
return value_;
}
// Only one thread/writer can increment/write the counter's value.
void Increase() {
// You can also use lock_guard here.
std::unique_lock<std::shared_mutex> lock(mutex_);
value_++;
}
// Only one thread/writer can reset/write the counter's value.
void Reset() {
std::unique_lock<std::shared_mutex> lock(mutex_);
value_ = 0;
}
private:
mutable std::shared_mutex mutex_;
std::size_t value_;
};
std::mutex g_io_mutex;
void Worker(Counter& counter) {
for (int i = 0; i < 3; ++i) {
counter.Increase();
std::size_t value = counter.Get();
std::lock_guard<std::mutex> lock(g_io_mutex);
std::cout << std::this_thread::get_id() << ' ' << value << std::endl;
}
}
int main() {
const std::size_t SIZE = 2;
Counter counter;
std::vector<std::thread> v;
v.reserve(SIZE);
v.emplace_back(Worker, std::ref(counter));
v.emplace_back(Worker, std::ref(counter));
for (std::thread& t : v) {
t.join();
}
return 0;
}
输出结果:
140561343293184 1
140561343293184 3
140561343293184 4
140561334900480 2
140561334900480 5
140561334900480 6