lock_guard
lock_guard是C++中提供的对互斥锁有关操作的高级接口,可对互斥锁进行自动上锁和解锁,多用于作为局部变量。(在对象创建时,构造函数中自动为传入的互斥锁对象上锁,局部变量被系统回收时,其析构函数自动对互斥锁对象进行解锁)
代码如下
#include <iostream>
#include <thread>
#include<mutex>
using namespace std;
//共享变量
int a = 0;
//定义信号量mutex
mutex mtx;
void add()
{
for (int i = 0; i < 100; i++)
{
//等价于mtx.lock + mtx.unlock
lock_guard<mutex> lg(mtx);
a++;
}
}
int main()
{
thread thread1(add);
thread thread2(add);
thread1.join();
thread2.join();
cout << "共享变量a = " << a << endl;
return 0;
}
unique_lock
unique_lock是比lock_guard更加强大的对互斥锁管理的接口,除了对互斥锁的自动上锁和解锁,还有延时上锁等功能,因此使用频率更高,开发中更多地使用这个接口。
void add2()
{
for (int i = 0; i < 100; i++)
{
//unique_lock也可以进行自动上锁和解锁与lock_guard等效
unique_lock<mutex> ul(mtx);
a++;
}
}
int main()
{
thread thread1(add2);
thread thread2(add2);
thread1.join();
thread2.join();
cout << "共享变量a = " << a << endl;
return 0;
}
//延迟锁,mutex不允许延迟操作
timed_mutex t_mtx;
void adde()
{
for (int i = 0; i < 100; i++)
{
unique_lock<timed_mutex> ul(t_mtx, defer_lock);//加上后续的defer_lock代表不要自动加锁
// ul.try_lock_until() 很少使用,无法获取互斥锁就阻塞到某个时间点,过点则返回
bool flag = ul.try_lock_for(chrono::seconds(5)); //若此时无法获取互斥锁,则只会阻塞5秒,拿到锁返回true,否则返回false
if (flag)
a++;
}
}
int main()
{
thread thread1(add2);
thread thread2(add2);
thread1.join();
thread2.join();
cout << "共享变量a = " << a << endl;
return 0;
}
单例模式与call_once
我们在使用类时,有些类只需要创建一个对象即可满足我们的使用需求,这时我们可以使用单例模式来设计这个类
单例模式下
无参构造要放在私有域下,且需要禁用拷贝构造和重载=运算符
饿汉模式
饿汉模式,一开始就把对象创建好,即为饿汉模式
class Log {
private:
Log(){}//单例模式下,构造函数放在private域下
Log(const Log& l) = delete;
Log& operator=(const Log& log) = delete;
public:
static Log& GetInstanceHungry()
{
static Log log;//静态作用域下只有一个log对象,而提前创建好对象为饿汉模式
return log;
}
void printLog(string s)
{
cout << s << endl;
}
};
懒汉模式
需要使用时才将对象创建好为懒汉模式
//once_flag类型得放到类外,否则报错
static once_flag flag;
class Log2 {
private:
Log2(){}
Log2(const Log2& l) = delete;
Log2& operator=(const Log2& log) = delete;
public:
//需要用到再创建对象就是懒汉模式
static Log2& GetInstanceLazy() {
static Log2* l = nullptr;
//call_once当多个线程都要调用获取实例的的函数时,保证只有一个线程执行call_once
call_once(flag, [&]() {
if (l == nullptr)
l = new Log2;
});
return *l;
}
void printLog(string s)
{
cout << s << endl;
}
};
call_once与单例模式
以懒汉模式为例,当多个线程同时打印输出时,有可能产生一种情况两者同时进入获取对象的函数,则此时静态对象会被new两次,这与我们单例模式只需要创建一个对象的设计思路就不符合了
而C++提供的call_once函数则保证了,在多个线程访问创建实例的函数时,只有一个线程去创建这个实例,因此有且只有一个实例
而饿汉不存在资源竞争关系,因为一开始就分配好了,且只有一次
#include <iostream>
#include <thread>
#include <mutex>
#include <string>
using namespace std;
//单例模式,只需要创建一个对象的类,如日志类
//单例模式下又有饿汉模式和懒汉模式
class Log {
private:
Log(){}//单例模式下,构造函数放在private域下
Log(const Log& l) = delete;
Log& operator=(const Log& log) = delete;
public:
static Log& GetInstanceHungry()
{
static Log log;//静态作用域下只有一个log对象,而提前创建好对象为饿汉模式
return log;
}
void printLog(string s)
{
cout << s << endl;
}
};
//once_flag类型得放到类外,否则报错
static once_flag flag;
class Log2 {
private:
Log2(){}
Log2(const Log2& l) = delete;
Log2& operator=(const Log2& log) = delete;
public:
//需要用到再创建对象就是懒汉模式
static Log2& GetInstanceLazy() {
static Log2* l = nullptr;
//call_once当多个线程都要调用获取实例的的函数时,保证只有一个线程执行call_once
call_once(flag, [&]() {
if (l == nullptr)
l = new Log2;
});
return *l;
}
void printLog(string s)
{
cout << s << endl;
}
};
//饿汉模式
void test01()
{
Log::GetInstanceHungry().printLog("饿汉模式的Hello World");
}
//懒汉模式
void test02()
{
Log2::GetInstanceLazy().printLog("懒汉模式的Hello World2");
}
//call_once 使用懒汉的Log2
void printString()
{
Log2::GetInstanceLazy().printLog("call_once的Hello World2");
}
int main()
{
test01();
test02();
thread t1(printString);
thread t2(printString);
t1.join();
t2.join();
return 0;
}