当前位置: 首页 > news >正文

c++中多线程的用法

并发与多线程

线程并不是越多越好,需要独立的堆栈空间,线程之间的上下文切换要保存很多中间状态。

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)); // 创建并开始执行}// todofor (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函数;灵活度不如原生的lockif (!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();​     //todomutex2.unlock();mutex1.unlock();
}
void fun2(){mutex2.lock();mutex1.lock();​     //todostd::lock(mutex1,mutex2);  // 这种方式可以避免死锁, 此时需要两个分别解锁   该方法是一次锁定多个互斥量//  下面的不需要自己手动解锁std::lock_guard<std:mutex>  sbgard(mutex1,std::adopt_lock); // std::adopt_lock 是一个结构体对象,其一个标志作用表示互斥量已经lock过了不需要再次lockstd::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,没加上返回falseif (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对象,保存的类型是intstd::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 里面的值能多次getstd::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;  // 由于++ 不是原子操作,最终的结果不是 200000return 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 没第二个参数,则认为是truemy_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 );


http://www.mrgr.cn/news/43838.html

相关文章:

  • 【CSS Tricks】试试新思路去处理文本超出情况
  • lrzsz串口文件传输
  • 打造银行智能营销助手:大模型助力精准营销
  • 网页前端开发之Javascript入门篇(5/9):函数
  • Leetcode力扣刷题——704.二分查找二分搜索法
  • 360桌面助手意见反馈
  • 关于Excel将列号由字母改为数字
  • CSP-J/S 复赛算法 区间动态规划
  • 【黑马点评】 使用RabbitMQ实现消息队列——2.使用RabbitMQ监听秒杀下单
  • 华为平板与非华为电脑多屏协同及Bug处理
  • 五种IO模型与阻塞IO
  • 软考系统分析师知识点三:应用数学
  • 【11】纯血鸿蒙HarmonyOS NEXT星河版开发0基础学习笔记-模块化语法与自定义组件
  • fiddler抓包18-2_导出jmeter、postman脚本(带请求头)
  • 【C#生态园】走进微服务世界:六款必备工具深度剖析
  • 明星周边销售网站开发:SpringBoot技术全解析
  • C++关于链表基础知识
  • 性能测试学习2:常见的性能测试策略(基准测试/负载测试/稳定性测试/压力测试/并发测试)
  • RAG早已经过时,RAG-Fusion正当时
  • vulnhub靶场之hackablell