互斥可以对操作集合上锁,避免多个线程同时访问共享资源。这些操作集合可以看作是一次原子操作。
本文章的代码库:
https://gitee.com/gamestorm577/CppStd
1. 互斥类
c++提供了各种互斥类。
mutex
构造函数
默认构造函数构造一个处于未锁定状态的互斥
锁定
mutex提供lock和try_lock接口用于锁定互斥,unlock用于解锁互斥。
1.调用线程调用lock时,如果mutex为未锁定状态,那么调用线程锁住该互斥;如果mutex为锁定状态,那么调用线程阻塞,知道mutex被解锁。
2.调用线程调用try_lock尝试锁定mutex并立即返回。如果mutex为未锁定状态,那么try_lock返回true并锁住mutex;如果mutex为锁定状态,try_lock返回false。
3.调用线程调用unlock解锁互斥。
4.调用线程调用lock或者try_lock时必须不占有mutex,调用unlock时mutex必须被调用线程锁定,否则会出现未定义行为。
代码示例:
int num = 0;
std::mutex mutex;auto Func = [&](int id)
{for (int i = 0; i < 5; ++i){mutex.lock();++num;std::cout << id << ": " << num << std::endl;mutex.unlock();std::this_thread::sleep_for(std::chrono::milliseconds(100));}
};std::thread t1(Func, 0);
std::thread t2(Func, 1);
t1.join();
t2.join();
可能的输出结果:
0: 1
1: 2
0: 3
1: 4
0: 5
1: 6
0: 7
1: 8
1: 9
0: 10
native_handle
返回mutex的底层实现句柄
timed_mutex
相比mutex,timed_mutex可以实现指定时间的锁定,对应的接口为try_lock_for和try_lock_until。
recursive_mutex
对于mutex,当某个执行线程已经占用mutex的情况下不能再调用lock或者try_lock,而recursive_mutex可以去掉这个限制。注意在占用mutex的情况下,调用recursive_mutex的lock或者try_lock也是有次数限制的。
recursive_time_mutex
和recursive_mutex的功能相同,同时增加了指定时间的锁定功能。
shared_mutex
shared_mutex有两个访问级别:共享性和独占性。如果一个执行线程获取了shared_mutex的独占行,那么其他线程无法获取该锁。当没有任何线程获取shared_mutex的独占性时,多个线程可以同时获取shared_mutex的共享性。
对于一些共享资源读取远多于写入操作的场景,shared_mutex可以提高并发性能。
shared_mutex的lock、try_lock接口用于获取独占性,unlock用于解锁独占性。lock_shared、try_lock_shared接口口用于获取共享性,unlock_shared用于解锁共享性。
代码示例:
auto Timer = [](std::function<void()> func) -> void
{auto start = std::chrono::system_clock::now();func();auto end = std::chrono::system_clock::now();std::chrono::duration<double, std::milli> time = end - start;std::cout << "use time: " << time.count() << std::endl;
};struct Test1
{void Func(){m_Mut.lock();std::this_thread::sleep_for(std::chrono::milliseconds(10));m_Mut.unlock();}std::mutex m_Mut;
};struct Test2
{void Func(){m_Mut.lock_shared();std::this_thread::sleep_for(std::chrono::milliseconds(10));m_Mut.unlock_shared();}std::shared_mutex m_Mut;
};auto Func1 = []()
{Test1 test1;std::vector<std::thread> threads(20);for (auto& t : threads){t = std::thread(&Test1::Func, &test1);}for (auto& t : threads){t.join();}
};auto Func2 = []()
{Test2 test2;std::vector<std::thread> threads(20);for (auto& t : threads){t = std::thread(&Test2::Func, &test2);}for (auto& t : threads){t.join();}
};Timer(Func1);
Timer(Func2);
输出结果:
use time: 326
use time: 33.2421
shared_timed_mutex
shared_timed_mutex和shared_mutex的功能相同,并且支持指定时间的锁定
2. lock类
c++提供了一些lock类用于方便地管理互斥类
lock_guard
lock_guard提供互斥体的RAII风格的包装。当创建lock_guard对象时,它试图占有给定互斥的所有权,当lock_guard离开作用域时,lock_guard被销毁并释放互斥体。代码示例:
struct MyMutex
{void lock(){std::cout << "MyMutex lock" << std::endl;}void unlock(){std::cout << "MyMutex unlock" << std::endl;}
};MyMutex mut;
std::lock_guard<MyMutex> lock(mut);
输出结果:
MyMutex lock
MyMutex unlock
scoped_lock
scoped_guard提供多个互斥体的RAII风格的包装。代码示例:
struct MyMutex
{void lock(){std::cout << "MyMutex lock: " << Id << std::endl;}bool try_lock(){std::cout << "MyMutex try_lock: " << Id << std::endl;return true;}void unlock(){std::cout << "MyMutex unlock: " << Id << std::endl;}int Id = 0;
};MyMutex mut1;
MyMutex mut2;
MyMutex mut3;
mut1.Id = 3;
mut2.Id = 17;
mut3.Id = 29;std::scoped_lock lock(mut1, mut2, mut3);
输出结果:
MyMutex lock: 3
MyMutex try_lock: 17
MyMutex try_lock: 29
MyMutex unlock: 3
MyMutex unlock: 17
MyMutex unlock: 29
unique_lock
unique_lock是一个通用的互斥包装器。
shared_lock
shared_lock是一个通用的共享互斥包装器。
3. 通用锁定算法
c++提供了std::try_lock、std::lock用于锁定互斥
4. 单次调用
c++提供了接口std::call_once来确保函数只被调用一次,无论是单线程还是多线程。代码示例:
struct Test
{void Func(){std::cout << "call Func: " << Id << std::endl;}void CallFuncOnce(){std::call_once(flag, &Test::Func, this);}std::once_flag flag;int Id = 0;
};Test test1;
Test test2;
test1.Id = 3;
test2.Id = 17;test1.CallFuncOnce();
test1.CallFuncOnce();
test1.CallFuncOnce();
test1.CallFuncOnce();std::jthread t1(&Test::CallFuncOnce, &test2);
std::jthread t2(&Test::CallFuncOnce, &test2);
std::jthread t3(&Test::CallFuncOnce, &test2);
输出结果:
call Func: 3
call Func: 17