并行指的是程序运行时的状态,就是同时运行的意思。
并发指的是程序的结构,这个程序程序同时执行多个独立的任务就说这个程序是并发的设计。
我们讲的代码结构就是指的并发
-
单核CPU产生并发:
某一个时刻只能执行一个任务,由操作系统调度,每秒钟进行多次“任务切换”,来实现并发的假象(不是真正的并发),切换任务时要保存变量的状态、执行进度等,存在时间开销。
-
多核CPU产生并发
双核,4核,8核,10核,能够实现真正的并行执行多个任务(硬件并发)
进程就是运行起来的可执行程序
可执行程序 :在Windows下扩展名为 exe的文件,Linux下为bin文件
线程:
- 进程可以包含多个线程。
- 主线程:从main函数开始,main函数执行完,主线程结束。
- 其他线程:需要我们自己创建,入口可以是函数、类、lambda表达式。
- 进程是否执行完毕的标志是主线程是否执行完。
实现并发的手段:
- 通过多个进程实现并发(多进程并发)
- 在单独的进程中,写代码创建除了主线程之外的其他线程来实现并发(多线程并发)
多线程并发由于线程间可以共享数据,开销远小于多进程并发,优先使用多线程并发。
构建子线程 thread 类成员函数 join示例:
#include// 使用多线程的头文件 #include #include #include
using namespace std; // 子线程 void putInData() { cout << "putInData 子线程 id = " << this_thread::get_id() << endl; for (int i = 0; i < 100; i++) { cout << "putInData 子线程:放入一个数据" << i << endl; //std::this_thread::sleep_for(std::chrono::milliseconds(5)); } } // 主线程从main函数入口开始执行 int main() { cout << "主线程 id = " << this_thread::get_id() << endl; //std::list dataQuene; //1、创建并开启线程,线程入口是 putInData 函数 thread putThread(putInData); //2、join() 阻塞主线程并等待子线程执行完,当子线程执行完毕,join()就执行完毕,主线程继续往下执行 //join意为汇合,子线程和主线程汇合 putThread.join(); //3、传统多线程程序中,主线程要等待子线程执行完毕,然后自己才能向下执行 //detach:分离,主线程不再与子线程汇合,不再等待子线程。!不建议使用 //detach后,子线程和主线程失去关联,驻留在后台,由C++运行时库接管 //putThread.detach(); // 4、joinable()判断是否可以成功使用join()或者detach() //如果返回true,证明可以调用join()或者detach() //如果返回false,证明调用过join()或者detach(),join()和detach()都不能再调用了 if (putThread.joinable()) { cout << "可以调用join()或者detach()" << endl; putThread.join(); } cout << "Hello, thread!" << endl; return 0; //主线程结束,则整个进程执行完毕 }
总结:
-
main函数开始后自动开启主线程
-
进程id: this_thread::get_id()可以查看线程id
-
thread类可以创建线程:创建后自动开启线程,线程入口可以是函数、类、lambda表达式
-
join:意为汇合,子线程和主线程汇合,阻塞主线程并等待子线程执行完,当子线程执行完毕,join()就执行完毕
-
detach:分离,主线程不再与子线程汇合,不再等待子线程
-
joinable:判断是否可以成功使用join()或者detach(),可以的话返回true,否则返回false
数据共享:
顾名思义,数据共享就是同一份数据有多个线程在使用,此时,如果同一个地址数据有读有写,如果不做处理,容易冲突崩溃。
互斥量 mutex-
互斥量就是个类对象,可以理解为一把锁,多个线程同一时间只有一个线程能加锁或解锁
-
lock, unlock 要成对使用,必须对应,否则出错
-
只保护需要保护的数据,控制好范围,少了达不到效果,多了影响效率。
示例:
示例分别通过函数和类成员函数创建子线程,两种方法有使用线程锁和不使用线程锁的两种模式。
#include#include // 多线程的头文件 #include #include
// 互斥锁头文件 using namespace std; // 设置模式 //#define THREAD_BY_FUNCTION #define THREAD_BY_CLASS #define USE_MUTEX #ifdef THREAD_BY_FUNCTION // 函数创建线程 std::list dataQuene; mutex myMutex; void putInData() { for (int i = 0; i < 1000; i++) //数字增大可增加冲突概率 { #ifdef USE_MUTEX myMutex.lock(); cout << "putInData 子线程:放入一个数据" << i << endl; dataQuene.push_back(i); myMutex.unlock(); #else cout << "putInData 子线程:放入一个数据" << i << endl; dataQuene.push_back(i); #endif } } void takeOutData() { int dataNum = 1000; while (dataNum) { if (!dataQuene.empty()) { #ifdef USE_MUTEX myMutex.lock(); cout << "takeOutData 子线程:取出一个数据" << dataQuene.front() << endl; dataQuene.pop_front(); dataNum--; myMutex.unlock(); #else cout << "takeOutData 子线程:取出一个数据" << dataQuene.front() << endl; dataQuene.pop_front(); dataNum--; #endif } } } #endif #ifdef THREAD_BY_CLASS // 类成员函数创建线程 class DealData { public: void putInData() { for (int i = 0; i < 1000; i++) { #ifdef USE_MUTEX myMutex.lock(); cout << "putInData 子线程:放入一个数据" << i << endl; dataQuene.push_back(i); myMutex.unlock(); #else cout << "putInData 子线程:放入一个数据" << i << endl; dataQuene.push_back(i); #endif } } void takeOutData() { //for (int i = 0; i < 10000; i++) // 会导致不能完全取出所有数据 while (dataNum) { if (!dataQuene.empty()) { #ifdef USE_MUTEX myMutex.lock(); cout << "takeOutData 子线程:取出一个数据" << dataQuene.front() << endl; dataQuene.pop_front(); dataNum--; myMutex.unlock(); #else cout << "takeOutData 子线程:取出一个数据" << dataQuene.front() << endl; dataQuene.pop_front(); dataNum--; #endif } } } private: std::list dataQuene; mutex myMutex; int dataNum = 1000; }; #endif int main() { //1、创建并开启线程,线程入口是 putInData,takeOutData 函数 #ifdef THREAD_BY_CLASS DealData myData; thread putThread(&DealData::putInData, ref(myData)); thread takeThread(&DealData::takeOutData, ref(myData)); #endif #ifdef THREAD_BY_FUNCTION thread putThread(&putInData); thread takeThread(&takeOutData); #endif takeThread.join(); putThread.join(); cout << "hello thread!" << endl; return 0; }
总结:
-
不管是使用哪种方法创建子线程,如果数据共享不使用互斥锁就会出现如下的错误信息:
分别采用了9种方法实现互斥锁,实际使用掌握一两种即可:
- lock和unlock
- lock_guard
- unique_lock
- adopt_lock
- defer_lock
- try_to_lock
- try_lock
- release
- move
#include多线程中死锁问题#include // 多线程的头文件 #include #include
// 互斥锁头文件 using namespace std; //#define USE_LOCK_UNLOCK //#define USE_LOCKGUARD //#define USE_UNIQUELOCK //#define USE_ADOPTLOCK //已经提前 lock //#define USE_DEFER_LOCK //互斥量没有lock,不能提前lock,只能后面 lock //#define USE_TRY_TO_LOCK //尝试去lock(不能提前lock),lock失败会返回,防止阻塞 //#define USE_RELEASE //解除所有权 //#define USE_MOVE //转移所有权 //#define USE_TRY_LOCK class DealData { public: void putInData() { for (int i = 0; i < 1000; i++) //数字增大可增加冲突概率 { #ifdef USE_LOCK_UNLOCK myMutex.lock(); cout << "putInData子线程:放入一个数据" << i << endl; dataQuene.push_back(i); myMutex.unlock(); #endif // USE_LOCK_UNLOCK #ifdef USE_LOCKGUARD lock_guard dataGuard(myMutex); cout << "putInData子线程:放入一个数据" << i << endl; dataQuene.push_back(i); #endif // USE_LOCKGUARD #ifdef USE_UNIQUELOCK unique_lock dataOutUnique(myMutex); cout << "putInData子线程:放入一个数据" << i << endl; dataQuene.push_back(i); #endif // USE_UNIQUELOCK #ifdef USE_ADOPTLOCK myMutex.lock(); // 使用std::adopt_lock 必须先lock,且不需要手动解锁 unique_lock dataControl(myMutex, std::adopt_lock); cout << "putInData子线程:放入一个数据" << i << endl; dataQuene.push_back(i); #endif // USE_ADOPTLOCK #ifdef USE_DEFER_LOCK unique_lock dataControl(myMutex, std::defer_lock); // std::defer_lock 使得myMutex没有加锁 myMutex.lock(); // 处理共享代码 cout << "putInData子线程:放入一个数据" << i << endl; dataQuene.push_back(i); myMutex.unlock(); #endif // USE_DEFER_LOCK #ifdef USE_TRY_TO_LOCK // 使用try_lock 功能类似 try_to_lock unique_lock dataControl(myMutex, std::try_to_lock); // 使用std::try_to_lock 之前不能lock,否则会lock 两次出错 if (dataControl.owns_lock()) { // 如果拿到了锁 cout << "putInData子线程:放入一个数据" << i << endl; dataQuene.push_back(i); } else { // 如果没拿到锁 cout << "没拿到锁,没法放入数据" << i << endl; i--; } #endif #ifdef USE_TRY_LOCK // 使用try_lock 功能类似 try_to_lock if (myMutex.try_lock() == true) { // 如果拿到了锁 cout << "putInData子线程:放入一个数据" << i << endl; dataQuene.push_back(i); myMutex.unlock(); } else { // 如果没拿到锁 cout << "没拿到锁,无法放入数据" << i << endl; i--; } #endif // USE_TRY_LOCK #ifdef USE_RELEASE // 把 dataControl 和 myMutex 绑定在一起,dataControl有myMutex的所有权 unique_lock dataControl(myMutex); // 释放了dataControl对myMutex的所有权,由pControl接管 std::mutex* pControl = dataControl.release(); cout << "putInData子线程:放入一个数据" << i << endl; dataQuene.push_back(i); pControl->unlock(); // 非共享数据 //.... #endif // USE_RELEASE #ifdef USE_MOVE unique_lock dataControl1(myMutex); //unique_lock dataControl2(dataControl1); // 不能复制所有权 unique_lock dataControl2(std::move(dataControl1)); // 可以移动所有权 cout << "putInData子线程:放入一个数据" << i << endl; dataQuene.push_back(i); #endif // USE_MOVE } } void takeOutData() { while (dataNum) { haveDta(); } } // 为了演示lock()、unlock()配对的重要性 void haveDta() { #ifdef USE_LOCK_UNLOCK myMutex.lock(); if (!dataQuene.empty()) { cout << "takeOutData子线程:取出一个数据" << dataQuene.front() << endl; dataQuene.pop_front(); dataNum--; myMutex.unlock(); // 不能忘记 return; } myMutex.unlock(); return; #endif // USE_LOCK_UNLOCK #ifdef USE_LOCKGUARD { lock_guard dataOutGuard(myMutex); if (!dataQuene.empty()) { cout << "takeOutData子线程:取出一个数据" << dataQuene.front() << endl; dataNum--; dataQuene.pop_front(); return; } return; } // xxxx //... #endif // USE_LOCKGUARD #ifdef USE_UNIQUELOCK if (!dataQuene.empty()) { unique_lock dataOutUnique(myMutex); cout << "takeOutData子线程:取出一个数据" << dataQuene.front() << endl; dataNum--; dataQuene.pop_front(); return; } return; #endif // USE_UNIQUELOCK // 默认的数据取出线程 unique_lock dataOutUnique(myMutex); if (!dataQuene.empty()) { cout << "takeOutData子线程:取出一个数据" << dataQuene.front() << endl; dataNum--; dataQuene.pop_front(); return; } return; } private: std::list dataQuene; mutex myMutex; int dataNum = 1000; }; int main() { //std::list dataQuene; //1、创建并开启线程,线程入口是 putInData,takeOutData 函数 DealData myData; thread putThread(&DealData::putInData, ref(myData)); thread takeThread(&DealData::takeOutData, ref(myData)); takeThread.join(); putThread.join(); cout << "thread done." << endl; return 1; }
死锁至少有两个互斥量,其产生原因如下:
- 线程A执行时,这个线程先锁mutex1,并且锁成功了,然后去锁mutex2的时候,出现了上下文切换。
- 线程B执行,这个线程先锁mutex2,因为mutex2没有被锁,即mutex2可以被锁成功,然后线程B要去锁mutex1。此时,死锁产生了,A锁着mutex1,需要锁mutex2,B锁着mutex2,需要锁mutex1, 两个线程没办法继续运行下去。
解决方案:
只要保证多个互斥量加锁的顺序一样就不会造成死锁
#include#include // 多线程的头文件 #include #include
// 互斥锁头文件 using namespace std; #define USE_LOCK_UNLOCK //#define USE_UNIQUELOCK class DealData { public: void putInData() { for (int i = 0; i < 1000; i++) //数字增大可增加冲突概率 { #ifdef USE_LOCK_UNLOCK myMutex1.lock(); myMutex2.lock(); cout << "putInData子线程:放入一个数据" << i << endl; dataQuene.push_back(i); myMutex1.unlock(); myMutex2.unlock(); #endif // USE_LOCK_UNLOCK #ifdef USE_UNIQUELOCK unique_lock goldenLock(myMutex1); unique_lock silverLock(myMutex2); cout << "putInData子线程:放入一个数据" << i << endl; dataQuene.push_back(i); #endif // USE_UNIQUELOCK } } void takeOutData() { while (dataNum) { #ifdef USE_LOCK_UNLOCK myMutex1.lock(); myMutex2.lock(); if (!dataQuene.empty()) { cout << "takeOutData子线程:取出一个数据" << dataQuene.front() << endl; dataQuene.pop_front(); dataNum--; } myMutex1.unlock(); myMutex2.unlock(); #endif // USE_LOCK_UNLOCK #ifdef USE_UNIQUELOCK unique_lock silverLock(myMutex2); unique_lock goldenLock(myMutex1); if (!dataQuene.empty()) { cout << "takeOutData子线程:取出一个数据" << dataQuene.front() << endl; dataQuene.pop_front(); } #endif // USE_UNIQUELOCK } } private: std::list dataQuene; std::mutex myMutex1; std::mutex myMutex2; int dataNum = 1000; }; int main() { //std::list dataQuene; //1、创建并开启线程,线程入口是 putInData,takeOutData 函数 DealData myData; thread putThread(&DealData::putInData, ref(myData)); thread takeThread(&DealData::takeOutData, ref(myData)); takeThread.join(); putThread.join(); std::cout << "thread done" << std::endl; return 1; }
总结:
- 可以看到存数据和取数据都是先给锁1加锁,再给锁2加锁,如果把取数据的加锁顺序改变,就可能会发生死锁(多执行几次)。
- 解锁的顺序并不会产生死锁现象
内容是学习课程所得总结,如果有不对的地方,或者有更好的见解,欢迎大家加我v:peak1229258698,一起交流学习。



