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 );