竞争状态与临界区
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));
}