并发与多线程
转自:🔥【github】
1、创建线程
调用线程函数:
可调用对象做参数:
- 线程的入口函数在对象的类重载()的函数
void operator()()
中,对象是值传递所以还必须有拷贝构造函数Obj(const &obj)
,这里对象是值传递1 2 3 4
| void operator()(){}; thread myThread(对象); void operator()(int val); thread myThread(对象,val);
|
lambda表达式:
1 2 3 4 5 6 7 8 9
| auto mylamthread = [] { ;} ```` 使用线程: ----------- * 实际只使用join():只有当所有线程运行结束后才运行主线程 ```cpp threadObj.detach(); threadObj.join(); threadObj.joinable(); //判断是否可以使用join()
|
2、线程传参
普通类型做线程参数
- 创建线程时,即使线程函数参数是&,主线程传递也依旧是值传递重新拷贝一份给线程函数。
1 2
| void func(int &var,){} thread myThread(func,var);
|
类对象做线程参数
- 传递类对象,应避免隐式类型转换,全部使用构建临时对象,线程函数必须用const &来接,避免再次构造对象。
1 2
| void func(const Obj &obj){} thread myThread(func,Obj(0));
|
- 如果非要用主线程的对象本身做线程参数
1 2 3 4 5
| void func(Obj &obj)
Obj obj; thread myThread(func,std::ref(obj));
|
用类成员函数指针做线程函数
1 2
| void threadWorkFunc(int val){}; thread myThread(&Obj::threadWorkFunc,&obj,val);
|
3、互斥量
mutex类
- 相当于一把锁。
lock()
与unlock()
必须成对使用,先lock
,再操作共享数据,然后unlock
lock_guard类模板
- 为了防止忘记unlock,引入
std::lock_guard
类模板,在定义时,构造函数中自动调用lock()
,在析构函数中自动调用unlock()
,直接取代unlock
lock
函数,不能共用;
- 使用:只需要在操作共享数据前加一行将互斥量加入模板即可,不需要考虑解锁
- 一般项目使用lock_guard就足够了
1
| std::lock_guard<std::mutex> mutexGuard(my_mutex);
|
死锁
- 至少有两个互斥量存在,在两个进程中,两个互斥量的
lock()
次序不同,就会引起死锁只要保持上锁的顺序一致就行
示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| class Obj{ private: std:List<int> MsgRecvQueue; mutex my_mutex; public: void outMsgRecvQueue(){ my_mutex.lock(); if(!MsgRecvQueue.empty()){ MsgRecvQueue.pop_front(); my_mutex.unlock(); }else{ my_mutex.unlock(); } }; void inMsgRecvQueue(){ my_mutex.lock(); MsgRecvQueue.push_back(); my_mutex.unlock(); }; };
Obj obj; std::thread myInMsgThread(&Obj::inMsgRecvQueue,&obj); std::thread myOutMsgThread(&Obj::outMsgRecvQueue,&obj); myInMsgThread.join(); myOutMsgThread.join();
|
4、单例模式与数据共享问题
构造函数私有化
本类指针类型的静态成员变量
返回本类指针得静态成员函数
- 对象只能创建一次
- 推荐主线程中创建对象(例如初始化配置信息),多线程只读访问,不需要互斥
- 线程中创建单例对象需要建立互斥
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class Obj{ private: Obj(){}; private: static Obj* obj; public: static Obj* GetInstance(){ if(obj==null){ std::lock_guard<std::mutex> mutexGuard(myMutex); if(obj==null) obj=new Obj; } return obj; } };
Obj* Obj::obj=null;
|
std::this_thread::get_id()
5、效率问题
双重锁定
- 使用两个判断,第一次提高效率,第二次只有加锁后的判断成立,才是真正的obj==bull
1 2 3 4
| if(obj==null){ std::lock_guard<std::mutex> mutexGuard(myMutex); if(obj==null) obj=new Obj;
|
条件变量
std::condition_variable
是一个类,函数waite()
等待通知notify_noce()
,收到通知后,将开启循环尝试拿锁
- 拿锁成功后,第二参数判断为true:往后执行代码
- 拿锁成功后,第二参数判断为false:继续休眠,等待
notify_noce()
通知 1 2 3 4
| 写数据线程1 std::lock_guard<std::mutex> mutexGuard(myMutex) dataQueue.push_back(1); my_condition.notyfy_one();
|
1 2 3 4 5 6 7 8
| 读数据线程2 std::lock_guard<std::mutex> mutexGuard(myMutex) my_condition(mutexGuard,[this]{ if(!dataQueue.empty()) return true; return false; }); dataQueue.pop_front();
|