竞争状态与临界区
- 1,基本互斥锁
- 2,try_lock
- 3,互斥锁存在的坑—线程抢占不到资源
- 4,超时锁
- 5,递归锁(在一个线程内可以多次lock的锁)recursive_mutex和recursive_timed_mutex用于业务组合
- 6,利用栈特性自动释放锁RALL
- 6.1手动实现简易RALL
- 6.2 c++11支持的RALL管理互斥资源 lock_guard
- 6.3 c++11 unique_lock c++11
1,基本互斥锁
//互斥锁使用
#include<mutex>
static mutex mux;
mux.lock();
mux.unlock();
案例
// An highlighted block
#include<iostream>
#include<thread>
#include<string>
#include<windows.h>
#include<chrono>
#include<mutex>
using namespace std;
static mutex mux;void TestThread1()
{mux.lock();cout << "=====================================" << endl;cout << "test 001" << endl;cout << "test 002" << endl;cout << "test 003" << endl;cout << "=====================================" << endl;mux.unlock();std::this_thread::sleep_for(std::chrono::milliseconds(50));
}void deal1()
{thread th(TestThread1);th.join();
}int main()
{deal1();
}
运行结果
互斥锁的作用在于可以尽可能的避免数据竞争,在lock与unlock之间的区域被称之为临界区,要注意一点的是,临界区要尽量小,锁要尽早的申请且尽早的释放。
2,try_lock
if (!mux.try_lock()){}
//尝试去锁,成功返回true,失败返回false
案例:
#include<windows.h>
#include<chrono>
#include<mutex>
using namespace std;
static mutex mux;void TestThread1()
{for (;;){if (!mux.try_lock()){cout << "." << "flush" << endl;;this_thread::sleep_for(std::chrono::milliseconds(50));continue;}//mux.lock();cout << "=====================================" << endl;cout << "test 001" << endl;cout << "test 002" << endl;cout << "test 003" << endl;cout << "=====================================" << endl;mux.unlock();std::this_thread::sleep_for(std::chrono::milliseconds(50));}}void deal1()
{for (int i = 0;i<10;i++){thread th1(TestThread1);th1.detach();}getchar();
}
int main()
{deal1();
}
注意,try_lock有性能开销。
3,互斥锁存在的坑—线程抢占不到资源
在2节中,unlock之后选择让线程睡眠50毫秒,为什么要要sleep 50毫秒呢???这就涉及到多线程并发时因多线程竞争cpu资源而容易出现的坑。
线程有这样几种状态,
1,初始化(Init): 表示该线程正在创建。
2,就绪(Ready):表示该线程在就绪列表中,等待CPU调度。
3,运行(Running): 表示该线程正在运行。
4,阻塞(Blocked):该线程被阻塞挂起,包括,锁,事件,信号量阻塞。此状态不占用cpu资源
5,退出:该线程结束,等待父线程回收资源。
一个线程在创建,就绪之后,肯直接进入阻塞状态,也可能先进入运行状态,后进入阻塞状态。当由运行状态进入阻塞状态之后,线程在阻塞一段时间之后可能因为等待互斥锁或条件变量,重新进入就绪态,等待cpu调度之后,才再次进入运行状态。
实际上,多线程并发运行,在某一个时间段之内其实只有一个线程在占用cpu资源,当某一个线程调用unlock之后,所有线程会再次去争夺cpu资源,此时会出现一个神奇的现象,那就是,某一个线程在unlock之后,cpu资源被释放,然后多个线程再次竞争cpu资源的时候,上一个占用cpu资源的线程再次获取到了cpu资源,导致某一个线程长期能抢占到cpu资源而其他线程就无法顺利运行下去,这种情况是我们在实际的项目当中不想遇到的,于是乎,在调用unlock之后,让现场 sleep 一段事件,这样就可以让其他线程争夺cpu资源,就不会出现某一个线程长期占用cpu资源的场景了。
4,超时锁
static timed_mutex tmux;
if (!!tmux.try_lock_for(chrono::milliseconds(500))){}
//尝试获取锁,当五百毫秒内没有获取到锁资源,就会返回false
案例:
#include<iostream>
#include<thread>
#include<string>
#include<windows.h>
#include<chrono>
#include<mutex>
using namespace std;
static timed_mutex tmux;void ThewadMain(int i)
{for (;;){if (!tmux.try_lock_for(chrono::milliseconds(500))){cout << i << " lock failed " << endl;continue;}else{cout << i << " [in]" << endl;tmux.unlock();std::this_thread::sleep_for(chrono::microseconds(2000));}}
}void deal1()
{for (int i = 0; i < 3; i++){thread th(ThewadMain,i);th.detach();}
}int main()
{deal1();getchar();
}
运行结果:
5,递归锁(在一个线程内可以多次lock的锁)recursive_mutex和recursive_timed_mutex用于业务组合
在我们开发某个项目的时候,随着时间的推移,项目代码越来越多,接口越来越多,会出现大量的接口层层调用的情况,肯会出现的一个情况是,某个外部接口已经对某个锁进行了 lock 但是调用的两一个接口也对某一个锁进行了lock ,同一个锁在同一个线程内连续调用两次lock会发生什么现象呢?
// An highlighted block
#include<iostream>
#include<thread>
#include<string>
#include<windows.h>
#include<chrono>
#include<mutex>
using namespace std;
static mutex mux;void Task1()
{mux.lock();cout << "task1" << endl;mux.unlock();
}void Task2()
{mux.lock();cout << "task2" << endl;mux.unlock();
}void ThewadMain(int i)
{mux.lock();Task1();Task2();mux.unlock();
}void deal1()
{thread th1(ThewadMain,1);
}int main()
{deal1();getchar();
}
我们发现,程序直接崩溃无法运行。
为了解决这个问题我们引入了递归锁,所谓的递归锁就是可以在一个线程中锁多次。
static recursive_mutex rmux;
#include<iostream>
#include<thread>
#include<string>
#include<windows.h>
#include<chrono>
#include<mutex>using namespace std;
static recursive_mutex rmux;void Task1()
{rmux.lock();cout << "task1" << endl;rmux.unlock();
}void Task2()
{rmux.lock();cout << "task2" << endl;rmux.unlock();
}void ThewadMain(int i)
{rmux.lock();Task1();Task2();rmux.unlock();
}void deal1()
{thread th1(ThewadMain,1);th1.detach();
}int main()
{deal1();getchar();
}
递归锁可以多次lock,但是也要相应的进行多次unlock,比如我lock两次,那么我也要相应的unlock两次。
6,利用栈特性自动释放锁RALL
RALL c++之父提出,使用局部对象来管理资源的技术成为资源获取即初始化,它的生命周期是由操作系统来管理的,无需人工介入;资源的销毁容易忘记,造成死锁或内存泄露。
简单来说就是,我们自己管理锁需要手动 lock 或者 手动 unlock ,但是随着项目周期的拉长我们很可能忘记 unlock 造成死锁,此时我们用一种方法可以不用手动实现 lock unlock 即可实现自动释放,尽可能的避免死锁或内存泄露。
6.1手动实现简易RALL
#include<iostream>
#include<thread>
#include<string>
#include<windows.h>
#include<chrono>
#include<mutex>using namespace std;
static mutex rmux;class XMutex
{
public:XMutex(mutex& mux):mux_(mux){mux_.lock();}~XMutex(){mux_.unlock();}
private:mutex& mux_;
};void ThreadMain(int i)
{XMutex lock(rmux);cout << i<<" in thread" << endl;
}void deal1()
{for (int i = 0; i < 10; i++){thread th(ThreadMain,i);th.detach();}
}int main()
{deal1();getchar();
}
6.2 c++11支持的RALL管理互斥资源 lock_guard
// A code block
static mutex rmux;
lock_guard<mutex> lock(rmux);
#include<iostream>
#include<thread>
#include<string>
#include<windows.h>
#include<chrono>
#include<mutex>
using namespace std;
static mutex rmux;void ThreadMain(int i)
{{lock_guard<mutex> lock(rmux);cout << i << " in thread" << endl;}std::this_thread::sleep_for(chrono::microseconds(200));}void deal1()
{for (int i = 0; i < 10; i++){thread th(ThreadMain,i);th.detach();}
}int main()
{deal1();getchar();
}
如上述代码所示,当出了大括号的范围,自动调用析构函数,会自动进行unlock,。
6.3 c++11 unique_lock c++11
上一个小结的 lock_guard 是一个最基本的简单的 RALL 锁,但实际的项目开发过程会出现更复杂的业务需求,比如会存在 一个锁赋值给另一个锁,一个锁移动到另一个锁,或者可能会出现临时手动解锁加锁,或者其他更复杂的需求,此时我们就会使用 unique_lock 。
// A code block
unique_lock c++11
支持临时释放锁 unlock
支持 adopt_lock (已经拥有锁,不加锁,出栈区会释放)
支持 defer_lock (延后拥有,不加锁,出栈区不释放)
支持 try_to_lock 尝试获得互斥的所有权而不阻塞,获取失败推出栈区不会释放,通过 owns_lock() 函数判断。
// An highlighted block
#include<iostream>
#include<thread>
#include<string>
#include<windows.h>
#include<chrono>
#include<mutex>
using namespace std;
static mutex rmux;void ThreadMain(int i)
{{unique_lock<mutex> lock(rmux);cout << i << " in thread" << endl;}std::this_thread::sleep_for(chrono::microseconds(200));}void deal1()
{for (int i = 0; i < 10; i++){thread th(ThreadMain,i);th.detach();}
}int main()
{deal1();getchar();
}
支持临时释放锁
// An highlighted block
void ThreadMain(int i)
{{unique_lock<mutex> lock(rmux);lock.unlock();cout << i << " in thread" << endl;lock.lock();}std::this_thread::sleep_for(chrono::microseconds(200));
}