C++11相关知识介绍差不多以后,我们来看看一些利用c++11特性实现的自定义锁。
正如名字所说,层次锁要求我们在加锁解锁时进行判断,也就是具有先后顺序,如果有低优先级的锁尝试在高优先级锁被上锁之后上锁就会报错。
#pragma once
//层次锁头文件
#include
#include
class hierarchical_mutex
{
public:
hierarchical_mutex(int Lv);
//创建时声明锁的等级
~hierarchical_mutex() = default;
void lock();
void unlock();
//private:
hierarchical_mutex(const hierarchical_mutex& another) = delete;
hierarchical_mutex operator =(const hierarchical_mutex& another) = delete;
//禁用拷贝构造和赋值函数
bool Compare_Hierarchy();
//比较优先级
unsigend long mutex_Level;
//锁的等级
unsigend long static Previous_level;
//先前锁的等级
std::mutex m_mtx;
};
#include "hierarchical_mutex.h"
thread_local long now_level = -1;
//thread_loacl变量表明当前锁的等级
unsigend long hierarchical_mutex::Previous_level = -2;
bool hierarchical_mutex::Compare_Hierarchy() {
//比较锁等级
if (mutex_Level > now_level)
return true;
else
return false;
}
void hierarchical_mutex::lock() {
if (!Compare_Hierarchy())
throw std::logic_error("Try to lock a Lv_higher mutex");
//比较返回false代表尝试越级加锁,抛出逻辑异常
else {
m_mtx.try_lock();
Previous_level = now_level;
now_level = mutex_Level;
}
}
void hierarchical_mutex::unlock() {
now_level = Previous_level;
m_mtx.unlock();
}
hierarchical_mutex::hierarchical_mutex(int Lv) {
if (Lv < 0)
throw std::logic_error("Lv Must bigger than 0");
//保证线程锁等级大于0
mutex_Level = Lv;
}
#include
#include
#include
#include
#include
#include "hierarchical_mutex.h"
using namespace std;
typedef hierarchical_mutex h_m;
//Test
h_m mtx1(50),mtx2(100); //给层次锁添加等级
void f1();
void f2();
void f1() {
//先锁等级50的mtx1,再进f2锁住等级100的mtx2,不会报错。
lock_guard<h_m> guard(mtx1);
cout << "f1" << endl;
f2();
}
void f2() {
lock_guard<h_m> guard(mtx2);
cout << "f2" << endl;
//f1();
//注释打开的话就会报错,不仅是死锁,而且还尝试了越级加锁(先f2再f1)
}
int main() {
thread t1(f1);
//thread t2(f2);//错误线程
t1.join();
//t2.join();
return 0;
}
层级锁很简单,多写就会了。
上述只是给出简单示范,不要用于实战(肯定有BUG,只是我没调试)。
接下来看看自旋锁。
自旋锁是一种用于保护多线程共享资源的锁,与一般的互斥锁(mutex)不同之处在于当自旋锁尝试获取锁的所有权时会以忙等待(busy waiting)的形式不断的循环检查锁是否可用,因为持有锁的时候始终在一个循环内旋转,所以叫自旋锁。在多处理器环境中对持有锁时间较短的程序来说使用自旋锁代替一般的互斥锁往往能提高程序的性能。(因为不用频繁加锁进行等待)。
自旋锁利用了atomic_flag来实现,这也是我们常说的无锁编程。
#pragma once
#include
using namespace std;
class spin_mutex
{
public:
spin_mutex() = default;
//禁用赋值运算符和拷贝函数
spin_mutex(const spin_mutex&) = delete;
spin_mutex& operator=(const spin_mutex) = delete;
public:
void lock() {
while (is_lock.test_and_set());
//检查flag是否被设置过,如果被设置过就代表加锁了,在此处循环。
}
void unlock() {
is_lock.clear();
//重置flag
}
private:
atomic_flag is_lock;
};
以上就是最简单的自旋锁的实现。
这里提供一下main代码
#include
#include
#include
#include "spin_mutex.h"
using namespace std
spin_mutex s_mtx;
void f1() {
s_mtx.lock();
cout << "f1" << endl;
s_mtx.unlock();
}
void f2() {
s_mtx.lock();
cout << "f2" << endl;
s_mtx.unlock();
}
int main() {
thread t1(f1);
thread t2(f2);
t1.join();
t2.join();
return 0;
}
信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用。在进入一个关键代码段之前,线程必须获取一个信号量;一旦该关键代码段完成了,那么该线程必须释放信号量。其它想进入该关键代码段的线程必须等待直到第一个线程释放信号量。为了完成这个过程,需要创建一个信号量VI,然后将Acquire Semaphore VI以及Release Semaphore VI分别放置在每个关键代码段的首末端。确认这些信号量VI引用的是初始创建的信号量。
通俗来说,信号量就是一个数字,初始值代表可进入线程数,每进入一个线程数字就减一,当数字小于0时,就代表有几个线程在等待,比如-3就代表三个线程在等待。
信号量可以在多线程编程下保证关键代码不被并发使用,也是一种类似于锁的机制。C++11并没有提供给我们这样的机制,但是提供了条件变量和锁,因此我们就可以自己来实现一个信号量。
#pragma once
#include
#include
#include
using namespace std;
class Semaphore {
public:
Semaphore::Semaphore(int value = 0) :m_value(value), m_Num(0), m_need_to_wake(0) {
}
void Signal() {
//可以理解为lock
unique_lock<mutex> ul_mtx(m_mtx);
if (++m_Num > m_value) {
m_need_to_wake++;
m_con.wait(ul_mtx);
}
}
void Wait() {
//可以理解为unlock
unique_lock<mutex> ul_mtx(m_mtx);
if (m_need_to_wake > 0) {
m_con.notify_one();
m_need_to_wake--;
}
}
private:
atomic_int m_value;
//信号量可支持多少个线程在运行
mutex m_mtx;
int m_Num, m_need_to_wake;
condition_variable m_con;
};