并发与多线程
线程并不是越多越好,需要独立的堆栈空间,线程之间的上下文切换要保存很多中间状态。
thread myobj(childThreadfunc); 接收一个可调用对象作为参数
myobj.join() 调用,阻塞主线程,让主线程等待,等子线程执行完了再继续;
myobj.detach(); 主线程不和子线程混合,主线程执行完之后并不影响子线程的执行;主线程执行完毕后,子线程的内容转移到后台执行
调用 detach() 之后不能再 join() 了,可以调用 joinable() 查看子线程是否可以 join()
1、创建多线程的几种方式
传递临时对象作为线程参数; 临时对象就是直接构造的对象 Animal( , , );构造出来的就是临时对象,可以直接在里面构造;
慎用detach;
传递类对象、智能指针作为线程参数;
std::ref(类的实例化对象) // 对于引用传递,用ref包一层,实现真正的引用传递,不会通过拷贝构造重新构造一个对象
void printf(unique_ptr<int> pan){
}
int main(int argc, char *argv[]) {
• unique_ptr<int> myp(new int(100));
• std::thread myobj(printf, std::move(myp) ); // move 的作用是将myp这个指针转到子线程中去; 这样pan这个指针指向的内存是myp的内存
}
用成员函数指针作为线程函数
ClassA myobj(10);
std::thread childthread(&A::thread_work,myobj,15); // 第一个参数是需要放到子线程的成员函数,第二个是该成员该数的实例化对象,第三个是该成员函数需要的参数
// 下面两种写法myobj不会调用拷贝构造函数重新拷贝一份
std::thread childthread(&A::thread_work,&myobj,15); // 第一个参数是需要放到子线程的成员函数,第二个是该成员该数的实例化对象,第三个是该成员函数需要的参数
std::thread childthread(&A::thread_work,std::ref(myobj),15); // 第一个参数是需要放到子线程的成员函数,第二个是该成员该数的实例化对象,第三个是该成员函数需要的参数
/**
线程入口函数
*/
void mythread() {
cout << "run the child thread" << endl;
MySingle *p_a = MySingle::GetInstance();
cout << "end" << endl;
return;
}
int main(int argc, char *argv[])
{
/**
// 第二个参数必须是引用,才能保证是线程安全,这样不会调用拷贝构造
TestClass testClass;
std::thread childThread(&TestClass::coutMsgRec, &testClass);
std::thread childThread1(&TestClass::isMsgRec, &testClass);
childThread.join();
childThread1.join();
*/
// 成员方法创建子线程
std::thread myobj1(mythread);
std::thread myobj2(mythread);
myobj1.join();
myobj2.join();
return 0;
}
#include <thread>
#include <vector>
#include <string>
#include <iostream>
using namespace std;
/**
线程入口函数
*/
void threadentry(int i) {
cout << i << " id : "<< std::this_thread::get_id() << " \n"<< endl;
// todo
}
int main(int argc, char *argv[])
{
// 传递临时对象作为线程参数;
vector<thread> childthread;
// 多个线程执行顺序是乱的,跟操作系统调度顺序有关;
for (int i = 0; i < 10; ++i) {
childthread.push_back(std::thread(threadentry, i)); // 创建并开始执行
}
// todo
for (auto iter = childthread.begin(); iter != childthread.end(); ++iter) {
iter->join(); // 等待是个线程都执行完,主线程才能结束
}
// TODO
return 0;
}
2、 数据共享问题 即读写锁的问题
互斥量(mutex)、锁、死锁 等情况
互斥量是一个类对象,相当于一把锁;其成员方法lock和unlock可以加锁和解锁
#include <thread>
#include <vector>
#include <string>
#include <iostream>
#include <mutex>
using namespace std;
class TestClass {
public:
void isMsgRec() {
for (int i = 0; i < 100;++i) {
m_mutex.lock(); // 这个锁只能解决写时的问题,即多个线程同时写时候,加锁解决冲突,但是读和写还是会有并发冲突
vec.push_back(i);
m_mutex.unlock();
cout << i << " " << std::this_thread::get_id() << "\n";
}
}
bool outMsgRecFlag(int &commd) {
// 操作容器的过程都要加锁,保证线程安全
m_mutex.lock(); // lock_gard 是一个类模板
// std::lock_guard<std::mutex> sbguard(m_mutex); // 加这行后所有的lock和unlock 都可以取消,在lock_guard 的构造函数里面执行了lock,
// 析构函数里面执行了unlock函数;灵活度不如原生的lock
if (!vec.empty()) {
int commd = vec.front();
vec.pop_back();
m_mutex.unlock(); // 每个分支都要解锁,几个出口就要几个解锁
return true;
}
m_mutex.unlock();
return false;
}
void coutMsgRec() {
for (int i = 0; i < 100; ++i) {
bool flag = outMsgRecFlag(i);
cout << i << " " << std::this_thread::get_id() << "\n";
}
}
private:
std::vector<int> vec;
std::mutex m_mutex;
};
int main(int argc, char *argv[])
{
TestClass testClass;
std::thread childThread(&TestClass::coutMsgRec, &testClass); // 第二个参数必须是引用,才能保证是线程安全,这样不会调用拷贝构造
std::thread childThread1(&TestClass::isMsgRec, &testClass);
childThread.join();
childThread1.join();
// TODO
return 0;
}
3、死锁问题
void func1(){
mutex1.lock();
mutex2.lock();
//todo
mutex2.unlock();
mutex1.unlock();
}
void fun2(){
mutex2.lock();
mutex1.lock();
//todo
std::lock(mutex1,mutex2); // 这种方式可以避免死锁, 此时需要两个分别解锁 该方法是一次锁定多个互斥量
// 下面的不需要自己手动解锁
std::lock_guard<std:mutex> sbgard(mutex1,std::adopt_lock); // std::adopt_lock 是一个结构体对象,其一个标志作用表示互斥量已经lock过了不需要再次lock
std::lock_guard<std:mutex> sbgard(mutex2,std::adopt_lock);
mutex1.unlock();
mutex2.unlock();
}
int main(int argc,char *args[]){
std::thread childthread1(func1);
std::thread childthread2(func1);
return 0;
}
解决方案: 互斥量加锁的顺序保持一致
4、unique_lock 的使用
unique_lock 是一个类模板,实际工作中推荐使用 lock_guard,其构造函数里面调用lock,析构函数里面调用unlock,使用起来不太灵活,
此时unique_lock 灵活了很多,但是效率上和内存占用上就更差一点了。
1) adopt_lock
std::unique_lock<std::mutex> qlock(m_mutex1,std::adopt_lock); //adopt_lock同样表示mutex1 这个互斥量已经在unique_lock的构造函数中lock了,不需要再次lock
std::chrono::milliseconds dura(2000);
std::this_thread::sleep_for(dura); // 线程休眠
加锁的语句,如果拿不到锁,就会一直卡在这个位置
2) try_to_lock
std::try_to_lock ,尝试用mutex的lock去锁定,如果没成功,也会立即返回,并不阻塞程序。
class TestClass {
public:
void isMsgRec() {
for (int i = 0; i < 1000;++i) {
// m_mutex1.lock();
// std::unique_lock<std::mutex> adgard(m_mutex1,std::adopt_lock); // 上面的加过了锁,adopt_lock 导致这行代码不会再加锁了
std::unique_lock<std::mutex> adlock(m_mutex1, std::try_to_lock); // 尝试去加锁,此时如果前面已经加锁了,这个锁还是会加,
// 因为没有adopt_lock 来说明这个锁是不可重入的
// 所以用try_to_lock 的前提是前面不能手动加锁
if (adlock.owns_lock()) {
vec.push_back(i);
cout << i << " ======" << std::this_thread::get_id() << "\n";
}
else {
// 没有拿到锁
cout << i << " no lock \n";
}
}
}
bool outMsgRecFlag(int &commd) {
std::unique_lock<std::mutex> qlock(m_mutex1);
std::chrono::milliseconds dura(2000);
std::this_thread::sleep_for(dura); // 线程休眠
if (!vec.empty()) {
int commd = vec.front();
vec.pop_back();
return true;
}
return false;
}
void coutMsgRec() {
for (int i = 0; i < 1000; ++i) {
bool flag = outMsgRecFlag(i);
cout << i << " ......" << std::this_thread::get_id() << "\n";
}
}
private:
std::vector<int> vec;
std::mutex m_mutex;
std::mutex m_mutex1;
};
int main(int argc, char *argv[])
{
TestClass testClass;
std::thread childThread(&TestClass::coutMsgRec, &testClass); // 第二个参数必须是引用,才能保证是线程安全,这样不好调用拷贝构造
std::thread childThread1(&TestClass::isMsgRec, &testClass);
childThread.join();
childThread1.join();
return 0;
}
3) defer_lock
使用这个参数的前提与try_to_lock 一样,不能手动去先 lock,否则会报异常
表示初始化了一个没有加锁的mutex
a) lock() 和unlock()的使用
std::unique_lock<std::mutex> adlock(m_mutex1, std::defer_lock);
adlock.lock(); // 加锁,此时会自动解锁,也可以手动解锁
vec.push_back(i);
cout << i << " ======" << std::this_thread::get_id() << "\n";
adlock.unlock(); // 临时解锁,然后处理非共享代码
// todo
adlock.lock(); // 处理完非共享数据后再次lock,处理共享代码
// todo
b) try_lock() 的使用
std::unique_lock<std::mutex> adlock(m_mutex1, std::defer_lock);
// try_lock 尝试加锁,加上返回true,没加上返回false
if (adlock.try_lock()) {
vec.push_back(i);
cout << i << " ======" << std::this_thread::get_id() << "\n";
}
else {
// todo
}
c) release 的使用
返回它所管理的mutex对象指针,并释放所有权,也就是所unique_lock 和mutex 不在关联
如果原来mutex处于加锁状态,需要接管过来并负责解锁
std::unique_lock<std::mutex> adlock(m_mutex1, std::defer_lock);
// try_lock 尝试加锁,加上返回true,没加上返回false
std::mutex *ptx = adlock.release(); // 上一行已经加锁了,此时接管后需要手动解锁
vec.push_back(i);
cout << i << " ======" << std::this_thread::get_id() << "\n";
ptx->unlock(); // 接管后手动解锁
// 此时ptx 的地址和m_mutex1 的地址是相同的
这个成员函数是为了控制锁的粒度,选择合适的粒度很重要
4) unique_lock 所有权的传递
所有权:一个mutex只能和一个unique_lock 绑定,不能绑定多个
a) 通过std::move 来转移所有权
std::unique_lock<std::mutex> adlock(m_mutex1);
std::unique_lock<std::mutex> adlock1(std::move(m_mutex1)); // move 是调用移动构造函数,另行绑定
std::mutex *ptx = adlock.release(); // 此时拿到的是空的
std::mutex *ptx = adlock1.release();
b) 通过return返回
std::unique_lock<std::mutex> rtn_unique_lock() {
std::unique_lock<std::mutex> tmpgard(m_mutex);
return tmpgard; // 从函数返回一个局部的对象是可以的,会通过移动构造函数返回一个临时的对象,并调用unique_lock 的移动构造函数
}
5 、其他保护共享数据的机制
1)单例设计模式
例如配置文件的读写等,整个项目只需要一个,始终只有一个对象
class MySingle {
public:
static MySingle *GetInstance() {
if (m_instance == nullptr) {
m_instance = new MySingle;
static CCarhuishou c1; // 程序退出的时候会调用这个类的析构函数,析构这个对象,静态变量的生命周期一直到程序退出
}
return m_instance;
}
// 类中类
class CCarhuishou {
public:
~CCarhuishou() {
if (MySingle::m_instance != nullptr) {
delete MySingle::m_instance;
MySingle::m_instance = nullptr;
}
}
};
private:
MySingle() {
;
};
static MySingle *m_instance;
};
int main(int argc, char *argv[])
{
MySingle *m_a = MySingle::GetInstance(); // 静态类直接通过类名可以调用,不需要通过类对象
MySingle *m_b = MySingle::GetInstance(); // m_a = m_b
return 0;
}
通常需要在主线程中,在调用其他线程之前,创建单例模式中的类,将该载入的数据载入其中,后续只读,这样子线程在操作这个单例的时候就是数据安全的
2) 单例设计模式解决共享数据
当需要在子线程中,而不是主线程中创建单例对象,就需要考虑线程安全问题
class MySingle {
public:
static MySingle *GetInstance() {
// 双重检查的单例模式
if (m_instance == nullptr) { // 如果不加这个非空判断,只有在第一次创建的时候锁才有作用,后续其他调用都是只读,影响效率
std::unique_lock<std::mutex> mylock(m_mutex); // 自动加锁,
if (m_instance == nullptr) {
m_instance = new MySingle; // 多个线程同时要访问的写内容
static CCarhuishou c1; // 程序退出的时候会调用这个类的析构函数,析构这个对象,静态变量的生命周期一直到程序退出
}
}
return m_instance;
}
// 类中类
class CCarhuishou {
public:
~CCarhuishou() {
if (MySingle::m_instance != nullptr) {
delete MySingle::m_instance;
MySingle::m_instance = nullptr;
}
}
};
private:
MySingle() {
;
};
static MySingle *m_instance;
};
/**
线程入口函数
*/
void mythread() {
cout << "run the child thread" << endl;
MySingle *p_a = MySingle::GetInstance();
cout << "end" << endl;
return;
}
int main(int argc, char *argv[])
{
/**
// 第二个参数必须是引用,才能保证是线程安全,这样不会调用拷贝构造
TestClass testClass;
std::thread childThread(&TestClass::coutMsgRec, &testClass);
std::thread childThread1(&TestClass::isMsgRec, &testClass);
childThread.join();
childThread1.join();
*/
// 成员方法创建子线程
std::thread myobj1(mythread);
std::thread myobj2(mythread);
myobj1.join();
myobj2.join();
return 0;
}
3) std::call_once() 的用法
c++ 11 引入的函数,其第二个参数是一个函数名,其功能是保证第二个参数表示的函数只能调用一次
具备互斥量的能力,而且效率上比互斥量消耗的资源更少。
需要与一个标记std::once_flag 结合使用,这个标记是一个结构;通过这个标记决定这个函数是否执行,第一次调用后设置为已调用状态
std::once_flag g_flag; //
class MySingle {
public:
static void createInstance() { // 只被调用一次
m_instance = new MySingle;
static CCarhuishou c1;
}
static MySingle *GetInstance() {
std::call_once(g_flag, createInstance);
return m_instance;
}
// 类中类
class CCarhuishou {
public:
~CCarhuishou() {
if (MySingle::m_instance != nullptr) {
delete MySingle::m_instance;
MySingle::m_instance = nullptr;
}
}
};
private:
MySingle() {
;
};
static MySingle *m_instance;
};
6、条件变量 condition_variable、wait、notify_one、notify_all
std::async 、std::future 创建后台任务并返回值;
std::async 是一个函数模板,用来启动一个异步任务,启动起来一个异步任务之后,返回一个std::future 对象,也是一个类模板
启动一个异步任务 就是自动创建一个线程并开始执行对应的入口函数,返回一个std::future对象,这个对象里面含有线程的入口函数所返回的结果
也就是线程返回值,通过get函数或者这个返回值
future 提供一种访问异步操作结果的机制;
1)、创建带返回值的异步线程
int mythread() {
cout << "start thread; ID" << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(9000);
std::this_thread::sleep_for(dura);
cout << "start end; ID" << std::this_thread::get_id() << endl;
return 5;
}
int main(int argc, char *argv[])
{
cout << "start main thread; ID" << std::this_thread::get_id() << endl;
std::future<int> result = std::async(mythread);
int def = 0;
// cout << "the result :" << result.get() << endl; // get() 函数是阻塞的,等待线程执行结束并返回值;才会结束 get只能调用一次
result.wait(); // 等待线程结束,本身并不返回结果,类似于join,阻塞在这里等子线程结束在继续下面的代码
cout << "test dd";
return 0;
}
类的成员函数做子线程
class ChildClass{
public:
int mythread(int mypr) {
cout << mypr;
cout << "start thread; ID" << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(9000);
std::this_thread::sleep_for(dura);
cout << "start end; ID" << std::this_thread::get_id() << endl;
return 5;
}
};
int main(int argc, char *argv[])
{
ChildClass childclass;
int tmpprt = 12;
cout << "start main thread; ID" << std::this_thread::get_id() << endl;
std::future<int> result = std::async(&ChildClass::mythread,&childclass,tmpprt); // 第二个参数是一个对象的引用
int def = 0;
// 如果get 和wait都不调用,主线程结束了子线程还在执行
// cout << "the result :" << result.get() << endl; // get() 函数是阻塞的,等待线程执行结束并返回值;才会结束 get只能调用一次
result.wait(); // 等待线程结束,本身并不返回结果,类似于join,阻塞在这里等子线程结束在继续下面的代码
cout << "test dd";
return 0;
}
std::launch 枚举类的用法
int main(int argc, char *argv[])
{
// 通过额外向std::async 传递一个参数,该参数是std::launch类型(枚举) 表达一些特殊含义
// std::launch::deferred 表示线程入口函数调用被延迟到std::future的wait 或者get函数调用的时候才执行
// 其实就是延迟调用,并且不会创建子线程,mythread是在主线程中执行的
ChildClass childclass;
int tmpprt = 12;
cout << "start main thread; ID" << std::this_thread::get_id() << endl;
// std::launch::async 会创建一个子线程并立马执行,(系统默认就是这个参数,跟不写launch一样)
std::future<int> result = std::async(std::launch::deferred, &ChildClass::mythread, &childclass, tmpprt); // 第二个参数是一个对象的引用
int def = 0;
cout << "the result :" << result.get() << endl; // get() 函数是阻塞的,等待线程执行结束并返回值;才会结束 get只能调用一次
//result.wait(); // 等待线程结束,本身并不返回结果,类似于join,阻塞在这里等子线程结束在继续下面的代码
cout << "test dd";
return 0;
}
2 、std::packaged_task 的用法
打包任务,把任务包装起来,也是一个类模板,模板参数是各种可调用参数,通过 std::packaged_task 把各种可调用对象包装起来,方便线程调用
包装一个成员方法
int mythread(int mypr) {
cout << mypr;
cout << "start thread; ID" << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(9000);
std::this_thread::sleep_for(dura);
cout << "start end; ID" << std::this_thread::get_id() << endl;
return 5;
}
int main(int argc, char *argv[])
{
int tmpprt = 12;
cout << "start main thread; ID" << std::this_thread::get_id() << endl;
std::packaged_task<int(int)> mypt(mythread); // int(int) 第一个int 是返回的结果类型,第二个是线程入口函数的参数类型
std::thread t1(std::ref(mypt), tmpprt); // 线程直接开始执行
cout << "test dd";
t1.join();
cout << "test aaaa";
std::future<int> result = mypt.get_future(); // 里面存的是返回的结果,需要通过get获取
cout << "result" << result.get();
return 0;
}
包装一个lambda表达式
int main(int argc, char *argv[])
{
int tmpprt = 12;
cout << "start main thread; ID" << std::this_thread::get_id() << endl;
// std::packaged_task<int(int)> mypt(mythread); // int(int) 第一个int 是返回的结果类型,第二个是线程入口函数的参数类型
std::packaged_task<int(int)> mypt([](int mypar) {
cout << mypar;
cout << "\n start thread; ID" << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(9000);
std::this_thread::sleep_for(dura);
cout << "\n start end; ID" << std::this_thread::get_id() << endl;
return 5;
});
std::thread t1(std::ref(mypt), tmpprt); // 线程直接开始执行
cout << "test dd \n";
t1.join();
cout << "test aaaa \n";
std::future<int> result = mypt.get_future(); // 里面存的是返回的结果,需要通过get获取
cout << " \n result" << result.get();
return 0;
}
将打包的内容放到容器里面
std::vector <std::packaged_task<int(int)>> mytack;
int main(int argc, char *argv[])
{
int tmpprt = 12;
cout << "start main thread; ID" << std::this_thread::get_id() << endl;
// std::packaged_task<int(int)> mypt(mythread); // int(int) 第一个int 是返回的结果类型,第二个是线程入口函数的参数类型
std::packaged_task<int(int)> mypt([](int mypar) {
cout << mypar;
cout << "\n start thread; ID" << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(9000);
std::this_thread::sleep_for(dura);
cout << "\n start end; ID" << std::this_thread::get_id() << endl;
return 5;
});
mytack.push_back(std::move(mypt));
std::packaged_task<int(int)> mypt2;
auto iter = mytack.begin();
mypt2 = std::move(*iter);
mytack.erase(iter); // 移除这一项,删除,删除后这个迭代器失效
cout << "babba";
mypt2(100); // 直接调用而不是构建子线程的话是单线程串行
return 0;
}
3) 、std::promise 类模板
能够在某个线程中给它赋值,然后在其他线程中将这个值取出来
实现两个线程中的数据传递
void mythread(std::promise<int> &tmp,int calc) {
calc++;
calc += 10;
cout << "start thread; ID" << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(9000);
std::this_thread::sleep_for(dura);
int result = calc; // 计算结果
tmp.set_value(result); // 在子线程中赋值
cout << "start end; ID" << std::this_thread::get_id() << endl;
}
void mythread2(std::future<int> &tmp) {
auto result = tmp.get();
cout << "thread2" << result;
}
int main(int argc, char *argv[])
{
std::promise<int> mypto; // 声明一个promise对象,保存的类型是int
std::thread t1(mythread, std::ref(mypto), 10);
t1.join();
// 获取结果值
std::future<int> fur = mypto.get_future(); // promise 和future 绑定,用于获取线程返回值
std::thread t2(mythread2,std::ref( fur));
//int res = fur.get();
t2.join();
// cout << "result " << res;
return 0;
}
4)、future_status 的用法
future_status 有三个状态,timiout 超时、ready 运行、deffered 延时,wait_for 为子线程返回一个状态
int mythread() {
cout << "start thread; ID" << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(5000);
std::this_thread::sleep_for(dura);
cout << "end thread ; ID" << std::this_thread::get_id() << endl;
return 5;
}
int main(int argc, char *argv[])
{
std::future<int> res = std::async(mythread);
cout << "wait for \n";
std::future_status status = res.wait_for(std::chrono::seconds(8)); // 等待
if (status == std::future_status::timeout) {
// 线程还没执行完,如果等待时间到,子线程还没执行完,子线程不会继续执行了
cout << "the thread is running \n";
}
else if (status == std::future_status::ready) {
cout << "res : " << res.get();
}
else if (status == std::future_status::deferred) { // 延迟 需要 async 的第一个参数设置为std::launch::deffer才成立
res.get(); // get 调用的时候才会执行子线程的内容
}
cout << "end" ;
return 0;
}
5)std::shared_future 的用法
第二次从future对象中调用get,程序会报异常,主要是因为get函数的设计,是一个移动 语义,第一次调用之后被移走之后就是空的了;
而shared_future 的get函数不再是转移,而是复制数据;
int mythread(int cnt) {
cout << "start thread; ID" << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(5000);
std::this_thread::sleep_for(dura);
cnt++;
cout << "end thread ; ID" << std::this_thread::get_id() << endl;
return 5;
}
std::vector <std::packaged_task<int(int)>> mytack;
int main(int argc, char *argv[])
{
cout << "main thread" << std::this_thread::get_id() << endl;
std::packaged_task<int(int)> mypt(mythread);
std::thread t1(std::ref(mypt),1);
t1.join();
std::future<int> result = mypt.get_future();
// 执行完毕后result里面的内容会移动到result_s,result_s 里面的值能多次get
std::shared_future<int> result_s(std::move(result)); // std::move 将左值转换成右值
// std::shared_future<int> result_s(result.share()); // 两种用法二选一
bool flag = result.valid(); // 能判断里面是否有值
if (flag) {
result.get();
}
}
6) 、std::atomic 原子操作
std::atomic 是一个模板
从汇编的角度看 ,++ 操作可能包含几个步骤,因此在多线程环境下这个操作并不是线程安全的,可以引入互斥量加锁来保证线程安全;
但是原子操作,可以认为是一种无锁的并发控制,表示这个步骤不能被打断,然后保证线程安全
原子操作就是不会被打断的代码,比加锁效率高
int g_cnt = 0;
/**
线程入口函数
*/
void mythread() {
for (int i = 0; i < 100000; ++i) {
g_cnt++;
}
}
int main(int argc, char *argv[])
{
thread t1(mythread);
thread t2(mythread);
t1.join();
t2.join();
cout << g_cnt; // 由于++ 不是原子操作,最终的结果不是 200000
return 0;
}
互斥量的加锁一般是针对一个代码段,而原子操作针对的是一个变量而不是代码段。
std::atomic<int> g_cnt = 0;
std::atomic<bool> m_flag = false;
原子操作只能处理变量的读写,赋值等简单操作,常用于计数、统计等工作;
原子操作一般针对于 ++ -- += -= &= |= 是支持的;
如果系统资源紧张 std ::thread 创建线程可能会失败;
std::async 创建一个异步任务;
std::launch::async 这个作为async的第一个参数,表示这个异步任务必须要创建一个新线程来执行任务
如果async不给第一个参数,系统默认是 std::launch::deferred | std::launch::async ;二者选其一,系统自动觉得使用哪种;
std::async 和std::thread 的区别
thread一定会创建线程,如果创建失败,程序会崩溃;
async不一定会创建新线程可能会失败;这是一个异步任务,且容易拿到返回值
系统资源紧张的时候async就不会创建新线程了,而是后续调用get的时候同步执行
std::future_status status = res.wait_for(std::chrono::seconds(0)); // 等待
if (status == std::future_status::deferred) {
// 异步任务被延迟调用,系统资源紧张了,采用了get调用时候同步执行的方式
res.get();
}
else if (status == std::future_status::ready) {
// 异步任务创建了新线程来执行
}
c++11 的互斥量是不允许连续调用两次的,但是在windows临界区是可以的
1)、递归的独占互斥量
7) 、wait / notify / notify_all notify_one的用法
都要与条件变量std::condition_variable 配合使用
std::condition_variable my_cond;
my_cond.wait(互斥量 , true| false); // 第二个参数是true,不堵塞,false,wait将解锁互斥量,并堵塞到本行
线程A: 等待一个条件满足
线程B:往消息队列里面扔消息;
线程B满足线程A 的条件后,触发线程A执行
class A {
public:
/**
向容器中写元素
*/
void inMsgRecQueue() {
for (int i = 0; i < 10000;++i) {
cout << "add an element" << endl;
std::unique_lock<std::mutex> aggard(m_mutex);
msgRecQue.push_back(i);
}
}
/**
从容器中取元素
*/
void outMSgRecQueue() {
int commd = 0;
for (int i = 0; i < 10000;++i) {
bool result = outMsg(commd);
if (result) {
cout << "get an element from queue \n";
}
else {
cout << "no element \n";
}
}
cout << "end \n";
}
/**
判断容器是否为空
*/
bool outMsg(int &commd) {
// 采用双重检查的方式判断降低锁的粒度,提高效率
if (!msgRecQue.empty()) {
std::unique_lock<std::mutex> sbgard1(m_mutex);
if (!msgRecQue.empty()) { // 操作共享数据需要加锁
commd = msgRecQue.back();
msgRecQue.pop_back();
return true;
}
}
return false;
}
private:
std::mutex m_mutex;
std::vector<int> msgRecQue;
};
int main(int argc, char *argv[])
{
A myobj;
std::thread myinThread(&A::inMsgRecQueue, &myobj);
std::thread myoutThread(&A::outMSgRecQueue,&myobj);
myinThread.join();
myoutThread.join();
return 0;
}
std::condition_variable 实际上是一个类,是一个和条件相关的类,必须和互斥量结合使用
class A {
public:
/**
向容器中写元素
*/
void inMsgRecQueue() {
for (int i = 0; i < 10000;++i) {
cout << "add an element \n" << endl;
std::unique_lock<std::mutex> aggard(m_mutex);
msgRecQue.push_back(i);
// 有元素后可以通知另一个线程取元素
my_condition.notify_one(); // 唤醒wait 的线程,当有多个线程wait的时候,只能唤醒其中一个
// my_condition.notify_all(); // 能唤醒所有在随眠的线程
}
}
/**
从容器中取元素
*/
void outMSgRecQueue() {
int commd = 0;
while (true) {
std::unique_lock<std::mutex> sbgard1(m_mutex);
// wait 是一个成员函数,第一个参数是一个互斥量,
// 第二个参数如果返回false,wait将解锁互斥量并堵塞到本行
// 堵塞到其他线程调用notify_one 为止
// 如果第二个参数是true,wait直接返回,相当于不等待
// 当其他线程将这个wait唤醒后,wait 将不断的尝试重新获取互斥量锁,如果获取不到,流程还是会阻塞在wait处
// 获取到之后并上锁,然后执行:
// 1) wait 有第二个参数,判断第二个参数的状态,当第二个参数任然是false,继续阻塞,第二个是true,则正常执行
// wait 没第二个参数,则认为是true
my_condition.wait(sbgard1, [this]() {
if (!msgRecQue.empty()) {
cout << "true \n";
return true;
}
cout << "false \n";
return false;
});
cout << "get element \n";
commd = msgRecQue.back();
msgRecQue.pop_back();
cout << "commd" << commd << endl;
sbgard1.unlock();
// 执行不用锁住的程序
}
}
/**
判断容器是否为空
*/
bool outMsg(int &commd) {
// 采用双重检查的方式判断降低锁的粒度,提高效率
if (!msgRecQue.empty()) {
std::unique_lock<std::mutex> sbgard1(m_mutex);
if (!msgRecQue.empty()) { // 操作共享数据需要加锁
commd = msgRecQue.back();
msgRecQue.pop_back();
return true;
}
}
return false;
}
private:
std::mutex m_mutex;
std::vector<int> msgRecQue;
std::condition_variable my_condition; // 生成一个条件变量对象
};
int main(int argc, char *argv[])
{
A myobj;
std::thread myinThread(&A::inMsgRecQueue, &myobj);
std::thread myoutThread(&A::outMSgRecQueue,&myobj);
std::thread myoutThread(&A::outMSgRecQueue, &myobj);
myinThread.join();
myoutThread.join();
return 0;
}
原子操作没有拷贝构造函数;
atomic<int> st1 = 0;
// atomic<int> st2 =st1 // 非法操作,系统不允许,没有拷贝构造函数
atomic<int> st2(st1.load());
标准库中的内容(std),在进入构造函数之前都会调用默认构造函数进行树池化,因此赋值操作是假的初始化;内置类型不一定(如 int );