加锁的最基本目的
线程的面对问题,简单的讲,就是线程安全问题。
数据增删改查,如果几个线程同时进行,则可能出现多删或多加入的情况,为了保证线程的安全,数据准确性,这时候必须要进行加锁。
C++ 11提供了四种锁 C++ 14和17又分别引进了新的锁
API | C++标准 | 释义 |
---|---|---|
mutex | C++11 | 最基本的互斥锁 |
timed_mutex | C++11 | 具有超时功能的互斥锁 |
recursive_mutex | C++11 | 递归锁(同一线程可重复加的互斥锁) |
recursive_timed_mutex | C++11 | 具有超时功能的递归锁 |
shared_mutex | C++17 | 读写锁(同一时间多个线程共享读的权限,只有一个线程有写的权限) |
shared_timed_mutex | C++14 | 具有超时功能的读写锁(看起来有点魔幻) |
最简单的锁入门
#include<iostream>
#include <thread>
#include <mutex>
using namespace std;
int counter_ = 0;
std::mutex mutex_;
/*
std::timed_mutex timed_mutex_;
void TestFunc1(int input)
{
if (timed_mutex_.try_lock_for(chrono::milliseconds(100))) {
for (int i = 0; i < input; i++) {
std::this_thread::sleep_for(chrono::milliseconds(10));
counter_++;
cout << "thread id=" << std::this_thread::get_id() << "counter_ =" << counter_ << endl;
}
timed_mutex_.unlock(); //释放锁
} else {
cout << "thread id=" << std::this_thread::get_id() << "counter_ =" << counter_ << endl;
}
}
*/
/*
//非递归锁,重复加锁会crash
std::recursive_mutex recursive_mutex_;
void TestFunc1(int input)
{
mutex_.lock();
for (int i = 0; i < input; i++) {
mutex_.lock();
counter_++;
std::this_thread::sleep_for(chrono::milliseconds(10));
cout << "thread id=" << std::this_thread::get_id() << "counter_ =" << counter_ << endl;
mutex_.unlock();
}
mutex_.unlock();
}
//递归锁,重复加锁
void TestFunc1(int input)
{
recursive_mutex_.lock();
for (int i = 0; i < input; i++) {
recursive_mutex_.lock();
counter_++;
std::this_thread::sleep_for(chrono::milliseconds(10));
cout << "thread id=" << std::this_thread::get_id() << "counter_ =" << counter_ << endl;
recursive_mutex_.unlock();
}
recursive_mutex_.unlock();
}
*/
void TestFunc1(int input)
{
mutex_.lock();
for (int i=0; i < input; i++) {
counter_++;
std::this_thread::sleep_for(chrono::milliseconds(10));
cout << "thread id=" << std::this_thread::get_id() << "counter_ =" << counter_ << endl;
}
mutex_.unlock();
}
int main()
{
cout << "main thread id=" << std::this_thread::get_id() << endl;
thread thread_1 = std::thread(TestFunc1,200);
thread thread_2 = std::thread(TestFunc1, 200);
thread_1.join();
thread_2.join();
cout << "thread id=" << std::this_thread::get_id() << "counter_ =" << counter_ << endl;
}
不加锁问题,等输出1的时候,1已经被第二个线程累加到2
互斥锁加锁后,保证了在当前id为8328线程执行完毕下一个线程才开始执行
超时互斥锁 由于9792线程一直占有所属权,1452线程超过等待时间直接输出
递归锁,在同一线程加锁/解锁,不对导致死锁
超时递归锁略
mutex锁的总结
C++ 几种锁的基础API
方法 | 说明 |
---|---|
lock | 锁定互斥体,如果不可用,则阻塞 |
try_lock | 尝试锁定互斥体,如果不可用,直接返回 |
unlock | 解锁互斥体 |
递归锁的特殊性
recursive_mutex和recursive_timed_mutex的名称都带有recursive。叫做可递归或者可重入锁,指在同一个线程中,同一把锁可以加锁多次。这就避免了一些不必要的死锁。
超时锁的特有API
方法 | 说明 |
---|---|
try_lock_for | 接受一个时间范围,在这一段时间范围之内线程如果没有获得锁则被阻塞住 |
try_lock_until | 接受一个时间点作为参数,在指定时间点未到来之前线程如果没有获得锁则被阻塞住,如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超时(即在指定时间内还是没有获得锁),则返回 false |
互斥锁和递归锁的特点示意
- 互斥锁 :某一时刻,只有一个线程可以获取互斥锁,在该互斥锁释放之前其他线程都不能获取该互斥锁。如果其他线程想要获取这个互斥锁,那么这个线程必须阻塞方式进行等待;
- 递归锁:线程有A、B、C方法。它们都会对同一个数据进行修改,那么必须加锁了,但某系功能需要,ABC可能会按特殊条件发生互相嵌套调用,如果你用互斥锁你就锁死了。但用递归锁允许同一个线程多次加锁,需要注意的是解锁的次数与加锁次数必须匹配;其他线程如果想访问数据也讲被阻塞,直至当前线程彻底释放递归锁后;