概述
文章目录
- 0.基本概念
- 1.进程
- 2.线程
- 3.并发
- 3.1 多进程并发
- 3.2 多线程并发
- 4.jion()
- 5.detach()
- 6.jionable()
- 7.注意
- 8.粒度
- 1.线程休眠
- 2.启动线程
- 3.传递参数
- 3.1传递临时对象作为线程参数
- 4.线程id
- 5.传递类对象、智能指针、类成员函数和类对象作为线程参数
- 5.1 传递类对象
- 5.2 传递智能指针
- 5.3 类成员函数
- 5.3 类对象
- 6.数据共享问题:
- 6.1 只读数据
- 6.2 读写数据
- 7.互斥量
- 7.1 互斥量的使用
- 7.2 std::lock_guard()
- 8.死锁
- 8.1概念
- 8.2 死锁演示
- 9.std::lock()函数模板 <用的比较少>
- 10.std::lock_guard的std::adopt_lock参数
- 11.unique_lock详解
- 11.1 unique_lock一般使用
- 11.2 unique_lock的第二参数
- 11.2.1 std::adopt_lock()
- 11.2.2 std::try_lock();
- 11.2.3 std::defer_lock;
- 12. uniuqe_lock的成员函数
- 12.1 lock() 加锁
- 12.2 unlock()解锁
- 11.2.3 try_lock():
- 11.2.3 release():
- 11.2.4 所有权
- 13.单例设计模式
- 14.std::call_once()
- 15.条件变量std::condition_variable、wait()、notify_one(),notify_all()
- 15.1 条件变量std::condition_variable、wait()、notify_one()
- 15.2 notify_all()
- 16. std::async 、std::future 创建后台任务并返回值
- 16.1 普通函数作为async()入口函数
- 16.2 采用成员函数作为async()入口函数:
- 16.3 std::async()第二参数:
- 16.4 std::async()深入
- 16.5 std::packaged_task:打包任务:
- 16.6 std::promise,类模板
- 17.std::future 的其他成员函数:
- 17.1 wait_for()
- 17.2 std::shared::future
- 18. 原子操作 std::atomic
- 18.1 引例
- 18.2 原子操作
- 18.3 原子操作深入
- 19. Windows临界区与std::recursive_mutex(递归的独占互斥量)
- 19.1 Windows临界区
- 19.2 std::recursive_mutex(递归的独占互斥量)
- 20.带超时的互斥量std::timed_mutex 和 std::recursive_timed_mutex
- 21.线程池
0.基本概念
1.进程
进程:就是运行起来的可执行程序;
2.线程
每一个进程(执行起来的可执行程序),都有一个主线程,这个主线程是唯一的,自动创建的,即:一个进程中只有一个主线程;
自己创建的线程一般称为子线程;
3.并发
3.1 多进程并发
进程之间的通信:
在同一个电脑上:管道,文件,消息队列,共享内存;
在不同电脑上:socket通信技术。
3.2 多线程并发
多线程并发,单个进程中,创建多个线程;
线程像一个轻量级的进程,每一个线程都有自己独立的运行的路径,但是一个进程中的所有线程共享地址空间(即共享内存),全局变量,指针,引用都可以在线程之间传播,所以使用多线程开销远远小于多进程。,但是,多线程中,共享内存,难以保证数据一致性问题。
4.jion()
jion():加入汇合,通俗的将就是等待或者阻塞,阻塞主线程的执行,直到子线程调用结束,然后子线程与主线汇合,然后主线程向下走;
5.detach()
detach():也就是主线程和子线程汇合,主线程与子线程各自执行,一旦detach(),这个主线程对象就会失去与这个主线程的关联,此时这个子线程就会驻留在后台运行(主线程与子线程失去联系),这个子线程就相当于被c++运行时库接管,由运行时库负责清理相关的资源。
一旦调用了detach(),就不能再用jion(),否则系统会报错。这是由于detach()之后,两条线程的执行速度不一致导致的。
6.jionable()
jionable:判断是否可以成功使用join()或detach()的。
7.注意
使用互斥量进行共享内存保护的时候,一般情况是在所需要进行保护的代码段进行lock()操作,只有lock()成功时,代码才能继续执行,否则就会一直lock()不在向下执行,直到lock()成功;
8.粒度
把锁头锁住的代码多少称为锁的粒度,粒度一般用粗细来描述;
- 锁住的代码少,这个粒度称之为细,执行效率高;
- 锁住的代码多,这个粒度称之为粗,执行效率低;
所以,在进行保护时,要注意粒度的大小,粒度太细,可能漏掉共享数据的保护,粒度太粗,影响效率;
1.线程休眠
#include <iostream>
#include <thread>
using namespace std;
int main()
{
std::this_thread::sleep_for(std::chrono::seconds(5)); //5s
std::this_thread::sleep_for(std::chrono::seconds(5)); //1秒 = 1000毫秒=10^6微秒
cout<<"5sn";
std::this_thread::sleep_for(std::chrono::microseconds(5*1000000)); //微妙
cout<<"5sn";
std::this_thread::sleep_for(std::chrono::milliseconds(5000)); //毫秒
cout<<"5sn";
std::this_thread::sleep_for(std::chrono::minutes(1));
cout<<"1minn";
std::this_thread::sleep_for(std::chrono::hours(5));
return 0;
}
2.启动线程
#include<iostream>
#include<thread> //A
using namespace std;
void print() //B
{
cout<<"-----------test-------------"<<endl;
}
int main()
{
cout<<"test start"<<endl;
thread t(print); //C 创建一个线程,其入口函数为print
t.join(); //D 等待线程结束,并加入主线程中
return 0;
}
多线程与普通程序相比较:
- 第一个区别是增加了
#include<thread>
,A,标准C++库中对多线程支持的声明在新的头文件中;管理线程的函数和类在<thread>
中声明,而保护共享数据的函数和类在其他文件中声明; - print函数需要独立,B,这是因为每一个线程都必须具有一个初始函数,新线程的执行从这里开始。对于应用程序而言,初始线程是
main()
,但是对于其他线程,可以在std::thread
对象的构造函数中指定;如:并命名为t的std::thread()
的对象拥有新函数print
作为其初始函数;与直接写入标准输出或是从main()调用print()不同,该程序启动了一个线程来实现,将线程数量一分为二——初始线程始于main(),而新线程始于print(); - 新的线程启动之后C,初始线程继续执行。如果它不等待新线程结束,它就将自顾自地继续运行到
main()
结束,从而结束程序,此时若新线程在main()
结束之前还没有没有结束,就会导致程序出错。所以,此处调用jion()
的原因,这会导致调用线程(main())
等待与std::thread
对象相关联的线程,即本例中t;
3.传递参数
3.1传递临时对象作为线程参数
- 指针在
detach
子线程中,传递若是指针值,所以主线线程执行完成后,子线程还没执行完,此时就会出错。所以不建议在子线程采用detach时,使用指针 - 只要用临时构造的类对象作为参数传递给线程,那么一定能够在主线程执行完毕之前把线程函数的参数构建出来,从而确保即便detach()子线程依然能够安全运行;
- 若传递int这种简单类型参数,建议都是值传递,不要采用引用;(detach)
- 如果传递类对象,避免隐式类型转换。全部都在创建线程时,就构造临时对象,然后在函数参数里,用引用来传递对象否则系统还会多次构造一次对象;(detach)
- 所以,建议使用jion(),这样就不存在局部变量失效导致线程对内存的非法引用问题;
#include<iostream>
#include<thread>
#include<mutex>
using namespace std;
class A //将整型转成A类型
{
public:
int m_i;
A(int a):m_i(a){cout<<"【A::A(int a)】构造函数执行t"<<this<<"子线程id:"<<std::this_thread::get_id()<<endl;}
A(const A &a):m_i(a.m_i){cout<<"【A::A(const A)】构造函数执行t"<<this<<"子线程id:"<<std::this_thread::get_id()<<endl;}
~A(){cout<<"【A::A(int a)】析构造函数执行t"<<this<<"子线程id:"<<std::this_thread::get_id()<<endl;}
};
void myprint(const int i,const A &pmybuf) //!采用引用,否则会被构造三次:此处需要使用const 否则出错:
//1.thread myobject(myprint,mvar,A(mysecondpar))中创建A(mysecondpar)临时对象时,调用一次构造函数;
//2. void myprint(const int i,const A &pmybuf)虽然这里采用引用传递,但是编译器在构造时,采用拷贝方式,
//目的是防止,在多线程中线程之间执行速度不一导致出现错误。
//3.?
{
cout<<&pmybuf<<endl;
return;
}
int main()
{
int mvar=1;
int myseconpar=12;
cout<<"主线程id:"<<this_thread::get_id()<<endl;
//thread myobject(myprint,mvar,myseconpar);//易出错!!
//myseconpar->A隐式转换是在调用子线程时执行,从而可能导致,main函数执行完毕后,myseconpar对象已经销毁,进而导致转换出错!
thread myobject(myprint,mvar,A(myseconpar));//myseconpar->A转换是在主线程中转换.
// myobject.detach();
myobject.join();
return 0;
}
4.线程id
线程id:id是一个数字,不同的线程,它的线程id必然不同;线程id可以同过c++标准库中的函数获取。std::this_thread::get_id()
或线程对象.get_id();
5.传递类对象、智能指针、类成员函数和类对象作为线程参数
5.1 传递类对象
std::ref()传递引用对象;
#include<iostream>
#include<thread>
#include<mutex>
using namespace std;
class A //将整型转成A类型
{
public:
int m_i;
A(int a):m_i(a){cout<<"【A::A(int a)】构造函数执行t"<<this<<"子线程id:"<<std::this_thread::get_id()<<endl;}
A(const A &a):m_i(a.m_i){cout<<"【A::A(const A)】构造函数执行t"<<this<<"子线程id:"<<std::this_thread::get_id()<<endl;}
~A(){cout<<"【A::A(int a)】析构造函数执行t"<<this<<"子线程id:"<<std::this_thread::get_id()<<endl;}
};
//!推荐使用
void myprint(A &pmybuf)
{
cout<<&pmybuf<<endl;
return;
}
int main()
{
int mvar=1;
int myseconpar=12;
cout<<"主线程id:"<<this_thread::get_id()<<endl;
A myobj(10);
std::thread mytobj(myprint,std::ref(myobj));//std::ref()引用传递
mytobj.join();
return 0;
}
5.2 传递智能指针
#include<iostream>
#include<thread>
#include<mutex>
using namespace std;
class A //将整型转成A类型
{
public:
int m_i;
A(int a):m_i(a){cout<<"【A::A(int a)】构造函数执行t"<<this<<"子线程id:"<<std::this_thread::get_id()<<endl;}
A(const A &a):m_i(a.m_i){cout<<"【A::A(const A)】构造函数执行t"<<this<<"子线程id:"<<std::this_thread::get_id()<<endl;}
~A(){cout<<"【A::A(int a)】析构造函数执行t"<<this<<"子线程id:"<<std::this_thread::get_id()<<endl;}
};
//!智能指针
void myprint(unique_ptr<int> ptr_)
{
cout<<"智能指针传递测试"<<endl;
return;
}
int main()
{
cout<<"主线程id:"<<this_thread::get_id()<<endl;
unique_ptr<int> ptr(new int (100));
//std::thread mytobj(myprint,ptr); //不能直接采用这种方式进行传递,需要依赖std::move()
std::thread mytobj(myprint,std::move(ptr)); //采用std::move()后,将会将指针ptr传递到ptr_但是,此时ptr就会置空(move移动);
mytobj.join();
return 0;
}
5.3 类成员函数
#include<iostream>
#include<thread>
#include<mutex>
using namespace std;
class A //将整型转成A类型
{
public:
int m_i;
A(int a):m_i(a){cout<<"【A::A(int a)】构造函数执行t"<<this<<"子线程id:"<<std::this_thread::get_id()<<endl;}
A(const A &a):m_i(a.m_i){cout<<"【A::A(const A)】构造函数执行t"<<this<<"子线程id:"<<std::this_thread::get_id()<<endl;}
~A(){cout<<"【A::A(int a)】析构造函数执行t"<<this<<"子线程id:"<<std::this_thread::get_id()<<endl;}
//! 成员函数作为子线程入口函数:
void thread_work(int a)
{
cout<<"子线程成员函数执行:t"<<this<<"子线程id:"<<std::this_thread::get_id()<<endl;
}
};
int main()
{
cout<<"主线程id:"<<this_thread::get_id()<<endl;
A myobj(10);
std::thread mytobj(&A::thread_work,&myobj,20); //表示的是myobj对象的thread_work成员函数作为多线程对象的
//mytobj的入口函数,并传递参数20;
//!此处&myobj等价于std::ref(myobj)
mytobj.join();
return 0;
}
5.3 类对象
需要重载运算符()
;
#include<iostream>
#include<thread>
#include<mutex>
using namespace std;
class A //将整型转成A类型
{
public:
int m_i;
A(int a):m_i(a){cout<<"【A::A(int a)】构造函数执行t"<<this<<"子线程id:"<<std::this_thread::get_id()<<endl;}
A(const A &a):m_i(a.m_i){cout<<"【A::A(const A)】构造函数执行t"<<this<<"子线程id:"<<std::this_thread::get_id()<<endl;}
~A(){cout<<"【A::A(int a)】析构造函数执行t"<<this<<"子线程id:"<<std::this_thread::get_id()<<endl;}
//! 成员函数作为子线程入口函数:
void thread_work(int a)
{
cout<<"子线程成员函数thread_work执行:t"<<this<<"子线程id:"<<std::this_thread::get_id()<<endl;
}
void operator()(int num) //任意A对象都可以作为线程的入口函数;
{
cout<<"子线程operator()执行:t"<<this<<"子线程id:"<<std::this_thread::get_id()<<endl;
}
};
int main()
{
cout<<"主线程id:"<<this_thread::get_id()<<endl;
A myobj(10); //类对象
std::thread mytobj(myobj,20); //类对象作为线程入口
//std::thread mytobj(std::ref(myobj),20); //采用引用的形式
mytobj.join();
return 0;
}
6.数据共享问题:
6.1 只读数据
是安全稳定的,不需要特别处理。
6.2 读写数据
数据读写时,需要进行相关处理,否则容易使得程序崩溃。
//此代码存在数据共享冲突!
#include <iostream>
#include <thread>
#include <list>
using namespace std;
class A
{
public:
void inMsgRecvQueue()
{
for (int i = 0; i < 1000000; ++i)
{
cout << "inMsgRecvQueue,执行插入一个元素" << i << endl;
msgRecvQueue.push_back(i);
}
}
void outMsgRecvQueue()
{
for (int i = 0; i < 1000000; ++i)
{
if (!msgRecvQueue.empty())
{
int commnd = msgRecvQueue.front(); //返回第一个元素
msgRecvQueue.pop_front(); //移除第一个元素
}
else
{
cout << "outMsgRecvQueue()消息为空!" << i << endl;
}
}
cout << "finished!" << endl;
}
private:
list<int> msgRecvQueue;
};
int main()
{
A myobja;
std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myOutMsgObj.join();
myInMsgObj.join();
return 0;
}
7.互斥量
互斥量是个类对象。相当于一把锁,多个线程尝试用lock()成员函数来加锁这把锁头,只有一个线程能锁定成功(成功的标志是lock()函数返回),若果没锁成功,那么就会在lock()这里不断尝试上锁;
7.1 互斥量的使用
#include<mutex>
lock(),unlock();
先lock(),操作共享数据,unlock();
注意:
- lock()与unlock()要成对使用,有lock()必须要有unlock(),每调用一次lock(),必然应该调用一次unlock();
- 不应该也不允许调用1次lock()却调用了2次unlock(),也不允许调用了两次lock()却调用了一次unlock(),这些非对称数量的调用,都会导致代码不稳定,甚至崩溃。
#include <iostream>
#include <thread>
#include <mutex>
#include <list>
using namespace std;
class A
{
public:
void inMsgRecvQueue()
{
for (int i = 0; i < 10000; ++i)
{
cout << "inMsgRecvQueue,执行插入一个元素" << i << endl;
//写数据时进行保护
my_mutex.lock();
msgRecvQueue.push_back(i); //保证在插入数据时,锁住程序,不被修改。
//写完后,进行解锁
my_mutex.unlock();
}
}
bool outMsgLULProc(int &command)
{
my_mutex.lock();
if (!msgRecvQueue.empty())
{
command = msgRecvQueue.front(); //返回第一个元素
msgRecvQueue.pop_front(); //移除第一个元素
my_mutex.unlock();
return true;
}
my_mutex.unlock();
return false;
}
void outMsgRecvQueue()
{
int command=0;
for(int i=0;i<10000;++i)
{
bool result=outMsgLULProc(command);
if(result==true)
{
cout<<"outMsgRecvQueue()执行,取出一个元素"<<endl;
}
else
{
cout<<"outMsgRecvQueue()为空。"<<endl;
}
}
cout << "finished!" << endl;
}
private:
list<int> msgRecvQueue;
std::mutex my_mutex;
};
int main()
{
A myobja;
std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myOutMsgObj.join();
myInMsgObj.join();
return 0;
}
7.2 std::lock_guard()
std::lock_guard()类模板,直接取代lock()与unlock(),用了lock_guard()之后,就不能在使用lock()与unlock();
#include <iostream>
#include <thread>
#include <mutex>
#include <list>
using namespace std;
class A
{
public:
void inMsgRecvQueue()
{
for (int i = 0; i < 10000; ++i)
{
cout << "inMsgRecvQueue,执行插入一个元素" << i << endl;
{
std::lock_guard<std::mutex> guard(my_mutex);
//写数据时进行保护
//my_mutex.lock();
msgRecvQueue.push_back(i); //保证在插入数据时,锁住程序,不被修改。
//写完后,进行解锁
//my_mutex.unlock();
}//通过此对大括号,提前解锁。guard相当于一个局部对象,提前析构,即解锁;
}
}
bool outMsgLULProc(int &command)
{
lock_guard<std::mutex> guard(my_mutex); //guard:构造函数执行了mutex::lock
//guard:析构函数执行了mutex::unlock();
// my_mutex.lock();
if (!msgRecvQueue.empty())
{
command = msgRecvQueue.front(); //返回第一个元素
msgRecvQueue.pop_front(); //移除第一个元素
my_mutex.unlock();
return true;
}
//my_mutex.unlock();
return false;
}
void outMsgRecvQueue()
{
int command = 0;
for (int i = 0; i < 10000; ++i)
{
bool result = outMsgLULProc(command);
if (result == true)
{
cout << "outMsgRecvQueue()执行,取出一个元素" << endl;
}
else
{
cout << "outMsgRecvQueue()为空。" << endl;
}
}
cout << "finished!" << endl;
}
private:
list<int> msgRecvQueue;
std::mutex my_mutex;
};
int main()
{
A myobja;
std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myOutMsgObj.join();
myInMsgObj.join();
return 0;
}
8.死锁
8.1概念
假如现在存在两把锁(死锁这个问题是至少两个锁头也就是两个互斥量才能产生)金锁(Jinlock),银锁(Yinlock),两个线程A,B,线程A执行的时候,这个线程先锁金锁,把金锁lock()成功,然后它准备去锁银锁,此时可能由于上下文切换,导致线程A去执行其他任务。
同样,线程B执行了,这个线程先锁银锁,因为银锁没有被锁,所以银锁会lock()成功,线程B要去锁金锁。
此种情况就导致了死锁的发生,线程A锁住金锁准备锁银锁,但是银锁被线程B锁定,此时线程A需要等待线程B解锁才能继续执行,但巧合的是,线程B锁住了银锁,需要进行锁金锁,但是线程A将金锁锁住,线程B需要等待线程A执行完毕,进行解锁之后才能继续执行,出现死锁;
8.2 死锁演示
#include <iostream>
#include <thread>
#include <mutex>
#include <list>
using namespace std;
class A
{
public:
void inMsgRecvQueue()
{
for (int i = 0; i < 100000; ++i)
{
cout << "inMsgRecvQueue,执行插入一个元素" << i << endl;
//!通过lock_guard()进行死锁演示:
std::lock_guard<std::mutex> guard1(my_mutex1); //! my_mutex1 先上锁,然后my_mutex2再上锁。
std::lock_guard<std::mutex> guard2(my_mutex2);
// my_mutex1.lock();
// my_mutex2.lock();
msgRecvQueue.push_back(i); //保证在插入数据时,锁住程序,不被修改。
// my_mutex2.unlock(); //! 解锁的先后顺序无关;
// my_mutex1.unlock();
}
}
bool outMsgLULProc(int &command)
{
std::lock_guard<std::mutex> guard1(my_mutex2);//! my_mutex2 先上锁,然后my_mutex1再上锁。
std::lock_guard<std::mutex> guard2(my_mutex1);
// my_mutex2.lock();
// my_mutex1.lock();
if (!msgRecvQueue.empty())
{
command = msgRecvQueue.front(); //返回第一个元素
msgRecvQueue.pop_front(); //移除第一个元素
// my_mutex1.unlock();
// my_mutex2.unlock(); //! 解锁的先后顺序无关;
return true;
}
// my_mutex1.unlock();
// my_mutex2.unlock();
return false;
}
void outMsgRecvQueue()
{
int command = 0;
for (int i = 0; i < 100000; ++i)
{
bool result = outMsgLULProc(command);
if (result == true)
{
cout << "outMsgRecvQueue()执行,取出一个元素" << endl;
}
else
{
cout << "outMsgRecvQueue()为空。" << endl;
}
}
cout << "finished!" << endl;
}
private:
list<int> msgRecvQueue;
std::mutex my_mutex1; //! 死锁产生的前提是需要两把锁,和两个线程!
std::mutex my_mutex2;
};
int main()
{
A myobja;
std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myOutMsgObj.join();
myInMsgObj.join();
return 0;
}
死锁的解决方法:只要保证这两个互斥量上锁的顺序一致就不会死锁,例如,现在有两互斥量锁std::mutex mymutex1与std::mutex mymutex2;
每次上锁的时,都先锁std::mutex mymutex1
,即mymutex1.lock()
,后锁mymutex2.lock()
就能够避免死锁的产生;
#include <iostream>
#include <thread>
#include <mutex>
#include <list>
using namespace std;
class A
{
public:
void inMsgRecvQueue()
{
for (int i = 0; i < 100000; ++i)
{
cout << "inMsgRecvQueue,执行插入一个元素" << i << endl;
//!通过lock_guard()进行死锁演示:
std::lock_guard<std::mutex> guard1(my_mutex1); //! my_mutex1 先上锁,然后my_mutex2再上锁。
std::lock_guard<std::mutex> guard2(my_mutex2);
// my_mutex1.lock();
// my_mutex2.lock();
msgRecvQueue.push_back(i); //保证在插入数据时,锁住程序,不被修改。
// my_mutex2.unlock(); //! 解锁的先后顺序无关;
// my_mutex1.unlock();
}
}
bool outMsgLULProc(int &command)
{
//! 为了避免死锁,所以要保证这两个互斥量上锁的顺序一致,在inMsgRecvQueue中先锁my_mutex1,故,此处也先锁my_mutex1;
std::lock_guard<std::mutex> guard2(my_mutex1);
std::lock_guard<std::mutex> guard1(my_mutex2);
// my_mutex1.lock();
// my_mutex2.lock();
if (!msgRecvQueue.empty())
{
command = msgRecvQueue.front(); //返回第一个元素
msgRecvQueue.pop_front(); //移除第一个元素
// my_mutex1.unlock();
// my_mutex2.unlock(); //! 解锁的先后顺序无关;
return true;
}
// my_mutex1.unlock();
// my_mutex2.unlock();
return false;
}
void outMsgRecvQueue()
{
int command = 0;
for (int i = 0; i < 100000; ++i)
{
bool result = outMsgLULProc(command);
if (result == true)
{
cout << "outMsgRecvQueue()执行,取出一个元素" << endl;
}
else
{
cout << "outMsgRecvQueue()为空。" << endl;
}
}
cout << "finished!" << endl;
}
private:
list<int> msgRecvQueue;
std::mutex my_mutex1; //! 死锁产生的前提是需要两把锁,和两个线程!
std::mutex my_mutex2;
};
int main()
{
A myobja;
std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myOutMsgObj.join();
myInMsgObj.join();
return 0;
}
9.std::lock()函数模板 <用的比较少>
一次锁住两个或者两个以上的互斥量(至少两个,多了不限,1个不行);它在多线程中不存在因为锁的顺序导致死锁的风险存在,这是因为如果其中一个互斥量中有一个没有锁住,它就会释放所有已经锁住的互斥量(这样就能有效的避免死锁的存在),然后重复上锁直到所有的锁都锁上才向下执行,否则继续释放已上锁的锁;
10.std::lock_guard的std::adopt_lock参数
std::adopt_lock是一个结构体对象,起一个标记作用:作用就是表示这个互斥量已经lock(),不需要在std::lock_guard<std::mutex>的构造函数里面对mutex对象进行再次lock()了;
#include <iostream>
#include <thread>
#include <mutex>
#include <list>
using namespace std;
class A
{
public:
void inMsgRecvQueue()
{
for (int i = 0; i < 100000; ++i)
{
cout << "inMsgRecvQueue,执行插入一个元素" << i << endl;
std::lock(my_mutex1,my_mutex2);//std::lock()函数模板,用来一次锁上两个或者两个以上的锁;为了避免在后面忘记解锁,
//采用带有std::adopt_lock()的std::lock_guard(),std::adopt_lock()的作用就是,不对已经上锁的对象进行再次上锁。
std::lock_guard<std::mutex> guard1(my_mutex1,std::adopt_lock);
std::lock_guard<std::mutex> guard2(my_mutex2,std::adopt_lock);
// my_mutex1.lock();
// my_mutex2.lock();
msgRecvQueue.push_back(i); //保证在插入数据时,锁住程序,不被修改。
// my_mutex2.unlock(); //! 解锁的先后顺序无关;
// my_mutex1.unlock();
}
}
bool outMsgLULProc(int &command)
{
//! 为了避免死锁,所以要保证这两个互斥量上锁的顺序一致,在inMsgRecvQueue中先锁my_mutex1,故,此处也先锁my_mutex1;
std::lock(my_mutex1,my_mutex2);
std::lock_guard<std::mutex> guard1(my_mutex1,std::adopt_lock);
std::lock_guard<std::mutex> guard2(my_mutex2,std::adopt_lock);
// my_mutex1.lock();
// my_mutex2.lock();
if (!msgRecvQueue.empty())
{
command = msgRecvQueue.front(); //返回第一个元素
msgRecvQueue.pop_front(); //移除第一个元素
// my_mutex1.unlock();
// my_mutex2.unlock(); //! 解锁的先后顺序无关;
return true;
}
// my_mutex1.unlock();
// my_mutex2.unlock();
return false;
}
void outMsgRecvQueue()
{
int command = 0;
for (int i = 0; i < 100000; ++i)
{
bool result = outMsgLULProc(command);
if (result == true)
{
cout << "outMsgRecvQueue()执行,取出一个元素" << endl;
}
else
{
cout << "outMsgRecvQueue()为空。" << endl;
}
}
cout << "finished!" << endl;
}
private:
list<int> msgRecvQueue;
std::mutex my_mutex1; //! 死锁产生的前提是需要两把锁,和两个线程!
std::mutex my_mutex2;
};
int main()
{
A myobja;
std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myOutMsgObj.join();
myInMsgObj.join();
return 0;
}
11.unique_lock详解
11.1 unique_lock一般使用
unique_lock
是一个类模板,工作中一般推荐使用lock_guard
;lock_guard取代了lock()与unlock();
unique_lock相对于lock_guard而言灵活
很多,但是效率差
一点,并且内存占用多
一点;
在一般不带参数使用时,unique_lock()
与lock_guard()
是相同的,unique_lock()
的灵活性主要体现在第二参数上。
#include <iostream>
#include <thread>
#include <mutex>
#include <list>
using namespace std;
class A
{
public:
void inMsgRecvQueue()
{
for (int i = 0; i < 100000; ++i)
{
cout << "inMsgRecvQueue,执行插入一个元素" << i << endl;
std::unique_lock<std::mutex> guard1(my_mutex1);
msgRecvQueue.push_back(i); //保证在插入数据时,锁住程序,不被修改。
}
}
bool outMsgLULProc(int &command)
{
std::unique_lock<std::mutex> guard1(my_mutex1);
if (!msgRecvQueue.empty())
{
command = msgRecvQueue.front(); //返回第一个元素
msgRecvQueue.pop_front(); //移除第一个元素
return true;
}
return false;
}
void outMsgRecvQueue()
{
int command = 0;
for (int i = 0; i < 100000; ++i)
{
bool result = outMsgLULProc(command);
if (result == true)
{
cout << "outMsgRecvQueue()执行,取出一个元素" << endl;
}
else
{
cout << "outMsgRecvQueue()为空。" << endl;
}
}
cout << "finished!" << endl;
}
private:
list<int> msgRecvQueue;
std::mutex my_mutex1;
};
int main()
{
A myobja;
std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myOutMsgObj.join();
myInMsgObj.join();
return 0;
}
11.2 unique_lock的第二参数
11.2.1 std::adopt_lock()
使用std::adopt_lock()
的前提是一定先lock()
,否则会报异常(windows
);
std::lock_guard<std::mutex> guard1(my_mutex1,std::adopt_lock);
adopt_lock
起标记作用。std::adopt_lock:
表示这个互斥量my_mutex1
已经被lock()
了,(在使用adopt_lock
作为第二参数时,必须确保所需互斥量(my_mutex1
)已经在前面lock()
了,否则报异常);std::adopt_lock()
的标记效果就是:假设调用方线程已经拥有了互斥量的所有权(已经lock
成功);通知lock_guard()
不需要在构造函数中lock()
这个互斥量;std::unique_lock
也可以带std::adopt_lock
标记,含义与std::lock_guard
相同,就是不希望再unique_lock()
的构造函数中lock()
这个mutex
;
...
my_mutex1.lock();//先要lock()后,才能用unique_lock()的std::adopt_lock()参数
std::lock_guard<std::mutex> guard1(my_mutex1,std::adopt_lock);
...
11.2.2 std::try_lock();
使用try_to_lock的前提是一定不要先lock(),否则会报异常(windows);
我们会尝试用mutex的lock()去锁这个互斥量,但如果没有锁定成功,就会立即返回,并不会阻塞在lock();
#include <iostream>
#include <thread>
#include <mutex>
#include <list>
#include<string>
using namespace std;
class A
{
public:
void inMsgRecvQueue()
{
int j = 0;
for (int i = 0; i < 100000; ++i)
{
//! std::try_to_lock
std::unique_lock<std::mutex> guard1(my_mutex1, std::try_to_lock);
if (guard1.owns_lock())
{
cout << "inMsgRecvQueue,执行插入一个元素" << i << endl;
cout << "拿到锁,开始插入第" + to_string(j) << "个数据" << endl;
msgRecvQueue.push_back(i); //保证在插入数据时,锁住程序,不被修改。
j++;
}
else //当没拿到锁的时候,会执行其他事,而不是一直等待lock
{
cout << "没拿到锁,干点其他事" << endl;
}
}
}
bool outMsgLULProc(int& command)
{
std::unique_lock<std::mutex> guard1(my_mutex1);
std::this_thread::sleep_for(std::chrono::milliseconds(200));//休眠200ms
if (!msgRecvQueue.empty())
{
command = msgRecvQueue.front(); //返回第一个元素
msgRecvQueue.pop_front(); //移除第一个元素
return true;
}
return false;
}
void outMsgRecvQueue()
{
int command = 0;
for (int i = 0; i < 100000; ++i)
{
bool result = outMsgLULProc(command);
if (result == true)
{
cout << "outMsgRecvQueue()执行,取出一个元素" << endl;
}
else
{
cout << "outMsgRecvQueue()为空。" << endl;
}
}
cout << "finished!" << endl;
}
private:
list<int> msgRecvQueue;
std::mutex my_mutex1;
};
int main()
{
A myobja;
std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myOutMsgObj.join();
myInMsgObj.join();
return 0;
}
11.2.3 std::defer_lock;
用std::defer_lock
的前提是一定不要先lock(),否则会报异常(windows);
std::defer_lock:表示并没有给mutex加锁,即初始话一个没有加锁的mutex,通常结合unique_lock的成员函数使用;
12. uniuqe_lock的成员函数
12.1 lock() 加锁
12.2 unlock()解锁
#include <iostream>
#include <thread>
#include <mutex>
#include <list>
#include<string>
using namespace std;
class A
{
public:
void inMsgRecvQueue()
{
int j = 0;
for (int i = 0; i < 100000; ++i)
{
cout << "inMsgRecvQueue,执行插入一个元素" << i << endl;
std::unique_lock<std::mutex> guard1(my_mutex1, std::defer_lock);//my_mutex1未加锁
guard1.lock();//调用成员函数加锁,不需要在调用unlock解锁,这是由于guard1在析构时解锁;
//guard1.unlock();//对于执行非保护数据时,可以采用成员函数unlock先进行解锁操作。
//不需要保护代码段...
//guard1.lock();上锁
msgRecvQueue.push_back(i);
}
}
bool outMsgLULProc(int& command)
{
std::unique_lock<std::mutex> guard1(my_mutex1);
if (!msgRecvQueue.empty())
{
command = msgRecvQueue.front(); //返回第一个元素
msgRecvQueue.pop_front(); //移除第一个元素
return true;
}
return false;
}
void outMsgRecvQueue()
{
int command = 0;
for (int i = 0; i < 100000; ++i)
{
bool result = outMsgLULProc(command);
if (result == true)
{
cout << "outMsgRecvQueue()执行,取出一个元素" << endl;
}
else
{
cout << "outMsgRecvQueue()为空。" << endl;
}
}
cout << "finished!" << endl;
}
private:
list<int> msgRecvQueue;
std::mutex my_mutex1;
};
int main()
{
A myobja;
std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myOutMsgObj.join();
myInMsgObj.join();
return 0;
}
11.2.3 try_lock():
try_lock():尝试给互斥量加锁,如果拿不到锁,就返回false,否则返回true,这个函数为非阻塞函数.
#include <iostream>
#include <thread>
#include <mutex>
#include <list>
#include<string>
using namespace std;
class A
{
public:
void inMsgRecvQueue()
{
int j = 0;
for (int i = 0; i < 100000; ++i)
{
cout << "inMsgRecvQueue,执行插入一个元素" << i << endl;
std::unique_lock<std::mutex> guard1(my_mutex1, std::defer_lock);//my_mutex1未加锁
if(guard1.try_lock()==true)//成员函数try_lock
{
msgRecvQueue.push_back(i);
}
else
{
cout<<"没拿到锁,干点其他事!"<<endl;
}
}
}
bool outMsgLULProc(int& command)
{
std::unique_lock<std::mutex> guard1(my_mutex1);
std::this_thread::sleep_for(std::chrono::milliseconds(2000));
if (!msgRecvQueue.empty())
{
command = msgRecvQueue.front(); //返回第一个元素
msgRecvQueue.pop_front(); //移除第一个元素
return true;
}
return false;
}
void outMsgRecvQueue()
{
int command = 0;
for (int i = 0; i < 100000; ++i)
{
bool result = outMsgLULProc(command);
if (result == true)
{
cout << "outMsgRecvQueue()执行,取出一个元素" << endl;
}
else
{
cout << "outMsgRecvQueue()为空。" << endl;
}
}
cout << "finished!" << endl;
}
private:
list<int> msgRecvQueue;
std::mutex my_mutex1;
};
int main()
{
A myobja;
std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myOutMsgObj.join();
myInMsgObj.join();
return 0;
}
11.2.3 release():
release():返回它所管理的mutex对象指针,并释放所有权;也就是说,这个unique_lock和mutex不在有关系。此时如果原来mutex对象处于加锁状态,需要手动解锁,否则出错!
#include <iostream>
#include <thread>
#include <mutex>
#include <list>
#include <string>
using namespace std;
class A
{
public:
void inMsgRecvQueue()
{
int j = 0;
for (int i = 0; i < 100000; ++i)
{
cout << "inMsgRecvQueue,执行插入一个元素" << i << endl;
std::unique_lock<std::mutex> guard1(my_mutex1); //构造成功时,加锁,加锁成功才能继续执行下面代码,否则等待加锁;
std::mutex *ptx=guard1.release();//!guard1释放my_mutex1,两者之间不存在任何关系,
//!此时需要手动解锁,因为unique_lock构造函数执行成功后,
//!才能执行到这一步,所以需要进行手动解锁;
msgRecvQueue.push_back(i); //!保护数据段
ptx->unlock(); //解锁
}
}
bool outMsgLULProc(int &command)
{
std::unique_lock<std::mutex> guard1(my_mutex1);
// std::this_thread::sleep_for(std::chrono::milliseconds(2000));
if (!msgRecvQueue.empty())
{
command = msgRecvQueue.front(); //返回第一个元素
msgRecvQueue.pop_front(); //移除第一个元素
return true;
}
return false;
}
void outMsgRecvQueue()
{
int command = 0;
for (int i = 0; i < 100000; ++i)
{
bool result = outMsgLULProc(command);
if (result == true)
{
cout << "outMsgRecvQueue()执行,取出一个元素" << endl;
}
else
{
cout << "outMsgRecvQueue()为空。" << endl;
}
}
cout << "finished!" << endl;
}
private:
list<int> msgRecvQueue;
std::mutex my_mutex1;
};
int main()
{
A myobja;
std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myOutMsgObj.join();
myInMsgObj.join();
return 0;
}
11.2.4 所有权
std::unique_lock<std::mutex> guard1(my_mutex1);
即guard1
拥有my_mutex1
的所有权,guard1
可以将机子对my_mutex1
的所有权转移给其他的unique_lock
对象,unique_lock
对象的所有权只能转移,不能复制!!
转移所有权的方式:
- std::move()
- return std::unique_lock<std::mutex>
#include <iostream>
#include <thread>
#include <mutex>
#include <list>
#include <string>
using namespace std;
class A
{
public:
//! 移动所有权方式2
std::unique_lock<std::mutex> rtn_unique_lock()
{
std::unique_lock<std::mutex> tempguard(my_mutex1);
return tempguard;//返回这种局部对象tempguard会导致系统生成临时unique_lock对象,并调用unique_lock的移动构造函数;
}
void inMsgRecvQueue()
{
for (int i = 0; i < 100000; ++i)
{
cout << "inMsgRecvQueue,执行插入一个元素" << i << endl;
//std::unique_lock<std::mutex> guard1(my_mutex1); //构造成功时,加锁,加锁成功才能继续执行下面代码,否则等待加锁;
//!移动所有权方式1
//std::unique_lock<std::mutex> guard2(std::move(guard1)); //!将guard1对my_mutex1所有权转移给guard2
//!移动所有权方式2
std::unique_lock<std::mutex> guard1=rtn_unique_lock();
msgRecvQueue.push_back(i); //!保护数据段
}
}
bool outMsgLULProc(int &command)
{
std::unique_lock<std::mutex> guard1(my_mutex1);
// std::this_thread::sleep_for(std::chrono::milliseconds(2000));
if (!msgRecvQueue.empty())
{
command = msgRecvQueue.front(); //返回第一个元素
msgRecvQueue.pop_front(); //移除第一个元素
return true;
}
return false;
}
void outMsgRecvQueue()
{
int command = 0;
for (int i = 0; i < 100000; ++i)
{
bool result = outMsgLULProc(command);
if (result == true)
{
cout << "outMsgRecvQueue()执行,取出一个元素" << endl;
}
else
{
cout << "outMsgRecvQueue()为空。" << endl;
}
}
cout << "finished!" << endl;
}
private:
list<int> msgRecvQueue;
std::mutex my_mutex1;
};
int main()
{
A myobja;
std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myOutMsgObj.join();
myInMsgObj.join();
return 0;
}
13.单例设计模式
单例设计模式,使用频率比较高。单例设计模式,是指某些特殊的类,属于该类的对象只能创建一个,多了创建不了。
#include <iostream>
#include <thread>
#include <mutex>
#include <list>
#include <string>
using namespace std;
class MyCAS //这是一个但单例类
{
private:
MyCAS(){} //私有化构造函数
private:
static MyCAS *m_instance;//静态成员变量,相当于公共空间,所有类对象都可以进行调用,用来释放m_instance空间;
public:
static MyCAS *GetIntance()
{
if(m_instance==NULL)
{
m_instance =new MyCAS();
static CGarhuishou cl;//静态对象在程序执行完成后进行析构;
//!CGarhuishou在后面进行定义的,为什么在前面没有报错?
}
return m_instance;
}
class CGarhuishou //嵌套类,用来释放对象
{
public:
~CGarhuishou()
{
if(MyCAS::m_instance)
{
delete MyCAS::m_instance;
MyCAS::m_instance=NULL;
}
}
};
void fun()
{
cout<<"test"<<endl;
}
};
MyCAS *MyCAS::m_instance=NULL;
int main()
{
MyCAS *p_a=MyCAS::GetIntance();//创建一个对象,返回该类(MyCAS)对象的指针;
MyCAS *p_b=MyCAS::GetIntance();//由于m_instance==NULL才进行构造,所以p_a与p_b是指向同一个对象;
p_a->fun();
//或
MyCAS::GetIntance()->fun();
}
上面程序在单个线程中使用时,是没问题的,但是若是多线程执行时,可能引发错误,错误的原因是:
if(m_instance==NULL)
{
m_instance =new MyCAS();
static CGarhuishou cl;
}
return m_instance;
//例如:存在两个线程,线程A与线程B,当线程A执行if(m_instance==NULL)时,此时进行上下文切换,导致线程A还没来得及执行
m_instance =new MyCAS();时,线程A执行其他程序,此时m_instance==NULL,倘若此时线程B也执行if(m_instance==NULL),此时就可
能导致 m_instance =new MyCAS(),执行了两次。则此时需要通过互斥量进行保护;
#include <iostream>
#include <thread>
#include <mutex>
#include <list>
#include <string>
using namespace std;
std::mutex resource_mutex;
class MyCAS //这是一个但单例类
{
private:
MyCAS() {} //私有化构造函数
private:
static MyCAS *m_instance; //静态成员变量,相当于公共空间,所有类对象都可以进行调用,用来释放m_instance空间;
public:
static MyCAS *GetIntance()
{
std::unique_lock<std::mutex> guard(resource_mutex);//这样虽然可以避免程序出错,但是影响执行效率。
//因为每执行一次GetIntance就需要进行上锁,但是
//我们只是希望在初始化的时候进行上锁,所以这样会降低
//程序的执行效率;
if (m_instance == NULL)
{
m_instance = new MyCAS();
static CGarhuishou cl; //静态对象在程序执行完成后进行析构;
//!CGarhuishou在后面进行定义的,为什么在前面没有报错?
}
return m_instance;
}
class CGarhuishou //嵌套类,用来释放对象
{
public:
~CGarhuishou()
{
if (MyCAS::m_instance)
{
delete MyCAS::m_instance;
MyCAS::m_instance = NULL;
}
}
};
void fun()
{
cout << "test" << endl;
}
};
MyCAS *MyCAS::m_instance = NULL;
//线程入口函数
void mythread()
{
cout << "线程开始执行....." << endl;
MyCAS *p_a = MyCAS::GetIntance(); //此时可能容易出问题;
cout << "线程开始执行....." << endl;
}
int main()
{
std::thread myobj1(mythread);
std::thread myobj2(mythread);
myobj1.join();
myobj2.join();
}
解决方案:
#include <iostream>
#include <thread>
#include <mutex>
#include <list>
#include <string>
using namespace std;
std::mutex resource_mutex;
class MyCAS //这是一个但单例类
{
private:
MyCAS() {} //私有化构造函数
private:
static MyCAS *m_instance; //静态成员变量,相当于公共空间,所有类对象都可以进行调用,用来释放m_instance空间;
public:
static MyCAS *GetIntance()
{
//! 提高效率:
//! 如果if(m_instance != NULL)条件成立,则肯定表示m_instance已经被new过了;
//! 如果if(m_instance == NULL)条件成立,不代表m_instance一定没被new过->上下文切换!!
if(m_instance == NULL) //双重锁定(双重检查)
{
std::unique_lock<std::mutex> guard(resource_mutex);
if (m_instance == NULL)
{
m_instance = new MyCAS();
static CGarhuishou cl; //静态对象在程序执行完成后进行析构;
//!CGarhuishou在后面进行定义的,为什么在前面没有报错?
}
}
return m_instance;
}
class CGarhuishou //嵌套类,用来释放对象
{
public:
~CGarhuishou()
{
if (MyCAS::m_instance)
{
delete MyCAS::m_instance;
MyCAS::m_instance = NULL;
}
}
};
void fun()
{
cout << "test" << endl;
}
};
MyCAS *MyCAS::m_instance = NULL;
//线程入口函数
void mythread()
{
cout << "线程开始执行....." << endl;
MyCAS *p_a = MyCAS::GetIntance(); //此时可能容易出问题;
cout << "线程开始执行....." << endl;
}
int main()
{
std::thread myobj1(mythread);
std::thread myobj2(mythread);
myobj1.join();
myobj2.join();
}
14.std::call_once()
std::call_once()
该函数的第二个参数是一个函数名 a();
std::call_once()
的功能是保证函数a()只会被调用一次(无论是单线程中多次被调用,或者在多线程中被多次调用);
std::call_once()
具备互斥量这种能力,而且在效率上,比互斥量的消耗的资源更少;
std::call_once()
需要与一个标记结合使用,这个标记std::once_flag
;std::once_flag
其实是一个结构,std::call_once()就是通过这个标记来决定对应a()函数对否执行,调用std::call_once()成功后,std::call_once()就会把这个标记设置为一种已调用的状态,后续再次调用std::call_once(),只要once_flag被设置为了“已调用”状态,那么对应的函数a()就不会再被执行;
#include <iostream>
#include <thread>
#include <mutex>
#include <list>
#include <string>
using namespace std;
std::mutex resource_mutex;
std::once_flag g_flag; //! for call_once()
class MyCAS //这是一个但单例类
{
static void CreatInstance() //只被调用一次
{
m_instance = new MyCAS();
static CGarhuishou cl; //静态对象在程序执行完成后进行析构->delete m_instance
}
private:
MyCAS() {} //私有化构造函数
private:
static MyCAS *m_instance; //静态成员变量,相当于公共空间,所有类对象都可以进行调用,用来释放m_instance空间;
public:
static MyCAS *GetIntance()
{
//! 两个线程同时执行到这里,其中一个线程要等待另一个线程执行完CreatInstance;
std::call_once(g_flag,CreatInstance);
return m_instance;
}
class CGarhuishou //嵌套类,用来释放对象
{
public:
~CGarhuishou()
{
if (MyCAS::m_instance)
{
delete MyCAS::m_instance;
MyCAS::m_instance = NULL;
}
}
};
void fun()
{
cout << "test" << endl;
}
};
MyCAS *MyCAS::m_instance = NULL;
//线程入口函数
void mythread()
{
cout << "线程开始执行....." << endl;
MyCAS *p_a = MyCAS::GetIntance(); //此时可能容易出问题;
cout << "线程开始执行....." << endl;
}
int main()
{
std::thread myobj1(mythread);
std::thread myobj2(mythread);
myobj1.join();
myobj2.join();
}
15.条件变量std::condition_variable、wait()、notify_one(),notify_all()
15.1 条件变量std::condition_variable、wait()、notify_one()
假若现在存在两个线程A与线程B,线程B向线程A发送一个信号时,线程A才开始向下执行。
std::condition_variable实际上是一个类,是一个和条件相关的一个类,等待一个条件达成,才能执行相关操作;这个类是需要和互斥量来配合工作的,用的时候我们要生成这个类的对象;
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable> //! for condition_variable
#include <list>
#include <string>
using namespace std;
class A
{
public:
//! 移动所有权方式2
std::unique_lock<std::mutex> rtn_unique_lock()
{
std::unique_lock<std::mutex> tempguard(my_mutex1);
return tempguard; //返回这种局部对象tempguard会导致系统生成临时unique_lock对象,并调用unique_lock的移动构造函数;
}
void inMsgRecvQueue()
{
for (int i = 0; i < 100000; ++i)
{
cout << "inMsgRecvQueue,执行插入一个元素" << i << endl;
std::unique_lock<std::mutex> guard1 = rtn_unique_lock();
msgRecvQueue.push_back(i); //!保护数据段
my_cond.notify_one(); //!假如此时outMsgRecvQueue()正在处理一个事物,而不是正卡在wait()哪里等待被唤醒,
//!那么此时这个notify_one()这个调用也许就没用的!
}
}
// bool outMsgLULProc(int &command)
// {
//双重锁定,双重验证,提高效率;
// if (!msgRecvQueue.empty())
// {
// std::unique_lock<std::mutex> guard1(my_mutex1);
// // std::this_thread::sleep_for(std::chrono::milliseconds(2000));
// if (!msgRecvQueue.empty())
// {
// command = msgRecvQueue.front(); //返回第一个元素
// msgRecvQueue.pop_front(); //移除第一个元素
// return true;
// }
// return false;
// }
// }
void outMsgRecvQueue()
{
int command = 0;
while (true)
{
std::unique_lock<std::mutex> guard(my_mutex1);
//! wait()用来等待一个条件:
//!如果第二个参数lambda表达式返回值是一个true,那么wait()直接返回,程序向下执行;
//!如果第二个参数lambda表达式返回值是一个false,那么wait()将解锁互斥量,并堵塞到本行
//!直到其他某个线程调用notify_one()成员函数为止;
//!如果wait()没有第二个参数:my_cond.wait(guard),那么就跟第二个参数lanbda表达式返回false效果一样;
//!wait()将解锁互斥量,并堵塞到本行,堵塞到其他某个线程调用notify_one()成员函数为止;
//!当其他线程用notify_one()将本wait(原来是堵塞状态)唤醒后,wait()开始工作:
//!(a)wait()不断地尝试重新获取互斥量锁my_mutex1,如果获取不到,那么线程就会卡在wait()这里等待获取,如果获取到了,wait()就继续执行(b);
//!(b)
//! b.1)如果wait有第二个参数(这里是lambda表达式),判断这个lambda表达式(或其他函数),如果表达式为false,那么wait又对互斥量解锁,然后有休眠等待再次被notify_one()唤醒;
//! b.2)如果lambda表达式为true,则wait()返回,程序向下执行(此时互斥量锁被锁着);
//! b.3)如果wait()没有第二个参数,则wait()返回,程序向下执行;
my_cond.wait(guard, [this] { //lambda表达式就是一个可调用对象
if (!msgRecvQueue.empty())
return true;
return false;
});
//程序执行在这个地方,此时互斥锁是锁着的
command = msgRecvQueue.front(); //返回第一个元素
msgRecvQueue.pop_front();
guard.unlock();
cout << "outMsgRecvQueue()取出一个元素:" << command << endl;
}
}
private:
list<int> msgRecvQueue;
std::mutex my_mutex1;
std::condition_variable my_cond;
};
int main()
{
A myobja;
std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myOutMsgObj.join();
myInMsgObj.join();
return 0;
}
//上面两个线程条件唤醒并不是一对一的,即并不是执行一次my_cond.notify_one(),等待线程不一定能被执行,即使等待线程能够被唤醒,但是两个线程依然需要竞争my_mutex1,这时候,可能唤醒线程先拿到锁,此时被唤醒的线程依然需要重新需要被my_cond.notify_one()重新唤醒。
15.2 notify_all()
假如现在不止一个线程在等待,而是多个线程在等待,则此时若想唤醒所有的线程,则采用notify_one()
显然是不合适的,因为notify_one()
只能唤醒一个线程。此时需要采用notify_all()
唤醒所有线程;
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable> //! for condition_variable
#include <list>
#include <string>
using namespace std;
class A
{
public:
//! 移动所有权方式2
std::unique_lock<std::mutex> rtn_unique_lock()
{
std::unique_lock<std::mutex> tempguard(my_mutex1);
return tempguard; //返回这种局部对象tempguard会导致系统生成临时unique_lock对象,并调用unique_lock的移动构造函数;
}
void inMsgRecvQueue()
{
for (int i = 0; i < 100000; ++i)
{
cout << "inMsgRecvQueue,执行插入一个元素" << i << endl;
std::unique_lock<std::mutex> guard1 = rtn_unique_lock();
msgRecvQueue.push_back(i);
// //! 唤醒一个等待线程!
// //! 现在主函数里面定义了两个等待线程,myOutMsgObj与myOutMsgObj2,此时若采用notify_one(),那么唤醒
// //! 线程是随机的,这是由于notify_one()一次只能唤醒一个线程;
// my_cond.notify_one();
//!唤醒所有等待线程!
//!现在主函数里面定义了两个等待线程,myOutMsgObj与myOutMsgObj2,两个线程具备唤醒,但是两个线程会同时向下
//!执行吗?答案是否定的,因为两个线程唤醒后都需要对互斥量my_mutex1进行加锁,而锁只有一把,所以只能有一个会成功
//!加锁,假如此时线程myOutMsgObj成功加锁,那么该线程会继续向下执行,myOutMsgObj2线程就会继续等待my_mutex1;
my_cond.notify_all();
}
}
void outMsgRecvQueue()
{
int command = 0;
while (true)
{
std::unique_lock<std::mutex> guard(my_mutex1);
my_cond.wait(guard, [this] { //lambda表达式就是一个可调用对象
if (!msgRecvQueue.empty())
return true;
return false;
});
//程序执行在这个地方,此时互斥锁是锁着的
command = msgRecvQueue.front(); //返回第一个元素
msgRecvQueue.pop_front();
guard.unlock();
cout << "outMsgRecvQueue()取出一个元素:" << command << endl;
}
}
private:
list<int> msgRecvQueue;
std::mutex my_mutex1;
std::condition_variable my_cond;
};
int main()
{
A myobja;
std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);
std::thread myOutMsgObj2(&A::outMsgRecvQueue, &myobja);
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myOutMsgObj.join();
myOutMsgObj2.join();
myInMsgObj.join();
return 0;
}
16. std::async 、std::future 创建后台任务并返回值
16.1 普通函数作为async()入口函数
希望通过线程返回一个结果;
std::async是一个函数模板,用来启动一个异步任务(就是自动创建一个线程并开始执行对应的线程的入口函数,他返回一个std::future对象),启动起来的一个异步任务之后,它返回一个std::future
对象,std::future
是一个类模板;
std::future
对象里面就含有线程入口函数所返回的结果(线程返回的结果),我们可以通过调用future
对象的成员函数get()
来获取结果,如果此时对应的子线程没有执行完毕,则会等待子线程执行完毕,get()之后不能重复get();
#include <iostream>
#include <map>
#include <string>
#include <thread>
#include <list>
#include <mutex>
#include <future>
using namespace std;
int mythread(int num)
{
int test=num;
cout<<"mythread() start"<<" threadid ="<<std::this_thread::get_id()<<endl;
std::this_thread::sleep_for(std::chrono::milliseconds(5000)); //5s
cout<<"mythread() end"<<" threadid ="<<std::this_thread::get_id()<<endl;
return test;
}
int main()
{
int num=127;
cout<<"main() start"<<" threadid ="<<std::this_thread::get_id()<<endl;
std::future<int> result=std::async(mythread,num);//创建一个线程并开始执行
cout<<"continue...!"<<endl;
int def=0;
//result.wait();//等待子线程结束,没有返回值;
cout<<result.get()<<endl;//等待子线程执行完,才能拿到结果!
cout<<"main done!"<<endl;
return 0;
}
16.2 采用成员函数作为async()入口函数:
#include <iostream>
#include <map>
#include <string>
#include <thread>
#include <list>
#include <mutex>
#include <future>
using namespace std;
class A
{
public:
int mythread(int num)
{
cout << "mythread() start"
<< " threadid =" << std::this_thread::get_id() << endl;
std::this_thread::sleep_for(std::chrono::milliseconds(5000)); //5s
cout << "mythread() end"
<< " threadid =" << std::this_thread::get_id() << endl;
return num;
}
};
int main()
{
int num = 127;
A myobja;
cout << "main() start"
<< " threadid =" << std::this_thread::get_id() << endl;
//!成员函数作为入口函数
std::future<int> result = std::async(&A::mythread,&myobja,num);//第二个是对象引用,才能保证是同一个对象;
cout << "continue...!" << endl;
int def = 0;
//result.wait();//等待子线程结束,没有返回值;
cout << result.get() << endl; //等待子线程执行完,才能拿到结果!
cout << "main done!" << endl;
return 0;
}
16.3 std::async()第二参数:
async()
接受std::launch
类型的参数(枚举类型),对async()
类型进行设置:
std::launch::async
:在调用async()函数时,开始创建线程,并开始从入口函数执行.std::launch::deferred
:表示线程入口函数被延迟到std::future
的wait()
或get()
成员函数调用时才执行,若std::future
的wait()
或get()
成员函数没有被调用,则不会执行入口函数!采用该种方式不会创建子线程;std::launch::async|std::launch::deferred
(默认值)系统二选一,具有不确定性;- 不带第二参数,只传递入口函数,默认第二参数为:
std::launch::async|std::launch::deferred
#include <iostream>
#include <map>
#include <string>
#include <thread>
#include <list>
#include <mutex>
#include <future>
using namespace std;
class A
{
public:
int mythread(int num)
{
cout << "mythread() start"
<< " threadid =" << std::this_thread::get_id() << endl;
std::this_thread::sleep_for(std::chrono::milliseconds(5000)); //5s
cout << "mythread() end"
<< " threadid =" << std::this_thread::get_id() << endl;
return num;
}
};
int main()
{
int num = 127;
A myobja;
cout << "main() start"
<< " threadid =" << std::this_thread::get_id() << endl;
//!成员函数作为入口函数
std::future<int> result = std::async(std::launch::async,&A::mythread,&myobja,num); //创建一个线程并开始执行
cout << "continue...!" << endl;
int def = 0;
//result.wait();//等待子线程结束,没有返回值;
cout << result.get() << endl; //等待子线程执行完,才能拿到结果!
cout << "main done!" << endl;
return 0;
}
16.4 std::async()深入
std::async()
有两个第二参数:
std::launch::deferred
:延迟调用;并且创建新线程,延迟future
对象调用.get()
或.wait()
函数是才执行mythread()
,如果没有调用,就不会执行入口函数mythread()
;std::launch::async
:强制创建一个线程,强制这个任务在新线程上执行,std::launch::async|std::launch::deferred
(默认值)系统二选一,具有不确定性;- 不带第二参数,只传递入口函数,默认第二参数为:
std::launch::async|std::launch::deferred
std::async()与std::thread()的区别: - std::thread()
- std::thread()创建线程,如果系统资源紧张,创建线程失败,这个程序就会崩溃;
- std::thread()创建线程,如果有返回值,可以通过引用,全局量或者指针的形式获取。
- std::async() 创建异步任务,可能创建线程,也可能不创建线程。并且可以通过future较为容易的拿到返回值;
- 如果系统资源有限时:
- std::thread()创建的线程 太多时,则可能创建线程失败,系统报告异常。
- std::async(),一般不会报异常崩溃,因为系统资源紧张导致无法创建新线程的时候,std::async()这种不带第二参数的调用,就不会创建新线程,而是后续谁调用了.get()来请求结果,那么这个异步任务就运行在这条get()语句所在的线程上。如果使用std::launch::async强制创建新的线程,系统资源紧张也是会导致奔溃。所以一个程序中创建的新线程,不宜超过100-200个;
16.5 std::packaged_task:打包任务:
是一个类模板,它的模板参数是各种可调用对象,;通过std::packaged_task 来把线程中可调用的结果包装起来,方便将来作为线程入口函数来调用;
#include <iostream>
#include <map>
#include <string>
#include <thread>
#include <list>
#include <mutex>
#include <future>
using namespace std;
int mythread(int num)
{
cout << "mythread() start"
<< " threadid =" << std::this_thread::get_id() << endl;
std::this_thread::sleep_for(std::chrono::milliseconds(5000)); //5s
cout << "mythread() end"
<< " threadid =" << std::this_thread::get_id() << endl;
return num;
}
int main()
{
int num = 127;
cout << "main() start"
<< " threadid =" << std::this_thread::get_id() << endl;
std::packaged_task<int(int)>mypt(mythread); //把函数mythread通过packaged_task包装起来
std::thread t1(std::ref(mypt),num); //线程直接开始执行,第二个参数作为线程入口函数的参数;
cout<<"jion()调用..."<<endl;
t1.join(); //等待线程结束;
cout<<"jion()调用结束"<<endl;
std::future<int> result=mypt.get_future();//std::future对象中包含线程入库函数的返回结果,
//这里result保存mythread返回的对象;
//! 与调用std::async(std::launch::async,&A::mythread,&myobja,num);相比,子线程会在
//! t1.jion();结束时返回结果,所以result.get()不用在等待线程结束。
cout<<result.get()<<endl;
cout << "main done!" << endl;
return 0;
}
联合lambda表达式:
td::packaged_task<int(int)>mypt([](int num){
cout << "mythread() start"
<< " threadid =" << std::this_thread::get_id() << endl;
std::this_thread::sleep_for(std::chrono::milliseconds(5000)); //5s
cout << "mythread() end"
<< " threadid =" << std::this_thread::get_id() << endl;
return num;
}); //把函数mythread通过packaged_task包装起来
mypt(125);
std::future<int> result=mypt.get_future();//std::future对象中包含线程入库函数的返回结果,
//这里result保存mythread返回的对象;
cout<<result.get()<<endl;
cout << "main done!" << endl;
return 0;
16.6 std::promise,类模板
作用同std::async()
相似用于在多线程中返回结果;
#include <iostream>
#include <map>
#include <string>
#include <thread>
#include <list>
#include <mutex>
#include <future>
using namespace std;
void mythread(std::promise<int> &tmpp,int calc) //第一个参数用于输出;
{
//做一些操作:
calc++;
calc *=10;
cout << "mythread"
<< " threadid =" << std::this_thread::get_id() << endl;
//....
std::this_thread::sleep_for(std::chrono::milliseconds(5000));
//...出结果
int result=calc; //保存结果
tmpp.set_value(result);
}
void mythread2(std::future<int> &tmpf)
{
auto result=tmpf.get();
cout<<"mythread2 result "<<result<<'t'<< std::this_thread::get_id() << endl;
return;
}
int main()
{
int num = 127;
cout << "main() start"
<< " threadid =" << std::this_thread::get_id() << endl;
std::promise<int> myprom; //声明一个std::promise对象myprom,保存类型为int;
std::thread t1(mythread,std::ref(myprom),num);
t1.join();
// //!获取结果值
std::future<int> ful=myprom.get_future(); //promise和future绑定,用于获取线程返回值;
// auto result=ful.get();
// cout<<"result = "<<result<<endl;
//将值传递给第二个线程
std::thread t2(mythread2,std::ref(ful));
t2.join();
cout << "main done!" << endl;
return 0;
}
17.std::future 的其他成员函数:
17.1 wait_for()
wait_for():返回当前执行的状态;
#include <iostream>
#include <map>
#include <string>
#include <thread>
#include <list>
#include <mutex>
#include <future>
using namespace std;
int mythread(int num)
{
int test=num;
cout<<"mythread() start"<<" threadid ="<<std::this_thread::get_id()<<endl;
std::this_thread::sleep_for(std::chrono::milliseconds(5000)); //5s
cout<<"mythread() end"<<" threadid ="<<std::this_thread::get_id()<<endl;
return test;
}
int main()
{
int num=127;
cout<<"main() start"<<" threadid ="<<std::this_thread::get_id()<<endl;
//std::future<int> result=std::async(mythread,num);
std::future<int> result=std::async(std::launch::deferred,mythread,num);//std::async()使用std::launch::deferred参数,std::future_status::deferred成立;
cout<<"continue...!"<<endl;
//!wait_for
std::future_status status=result.wait_for(std::chrono::seconds()); //等待1s
if(status==std::future_status::timeout) //超时,我想等你1s,希望你返回,你没有返回,
//那么status==std::future_status::timeout成立
{
//表示线程没有执行完毕
cout<<"超时!线程还没有执行完毕"<<endl;
}
else if(status==std::future_status::ready)
{
cout<<"线程执行完毕,返回"<<endl;
cout<<result.get()<<endl;
}
else if(status==std::future_status::deferred)
{
cout<<"函数延迟执行!"<<endl;
cout<<result.get()<<endl;
}
cout<<"main done!"<<endl;
return 0;
}
17.2 std::shared::future
std::future 中获取的数据只能调用一次get(),这是由于get()函数在设计时,采用移动语义。相当调用了std::move();
std::shared_future :也是一个类模板,不过get()函数是复制数据,所以可以多次get(),获取数据;
#include <iostream>
#include <map>
#include <string>
#include <thread>
#include <list>
#include <mutex>
#include <future>
using namespace std;
int mythread(int num)
{
cout << "mythread() start"
<< " threadid =" << std::this_thread::get_id() << endl;
std::this_thread::sleep_for(std::chrono::milliseconds(5000)); //5s
cout << "mythread() end"
<< " threadid =" << std::this_thread::get_id() << endl;
return num;
}
void mythread2(std::shared_future<int> &tmpf)
{
auto result=tmpf.get();
cout<<"mythread2 result "<<result<<'t'<< std::this_thread::get_id() << endl;
return;
}
int main()
{
int num = 127;
cout << "main() start"
<< " threadid =" << std::this_thread::get_id() << endl;
std::packaged_task<int(int)> mypt(mythread);
std::thread t1(std::ref(mypt), num);
cout << "jion()调用..." << endl;
t1.join(); //等待线程结束;
cout << "jion()调用结束" << endl;
std::future<int> result = mypt.get_future();
bool ifcanget = result.valid();
//!用result初始化result_s
//! a) std::shared_future<int> result_s(std::move(result));
std::shared_future<int> result_s(result.share()); //执行完毕后,result_s有值,result空了;
ifcanget=result.valid();
auto mythreadresult=result_s.get();
mythreadresult=result_s.get(); //多次调用
//将值传递给第二个线程
std::thread t2(mythread2,std::ref(result_s));
t2.join();
return 0;
}
18. 原子操作 std::atomic
18.1 引例
#include <iostream>
#include <map>
#include <string>
#include <thread>
#include <list>
#include <mutex>
#include <future>
using namespace std;
std::mutex my_mutex;
int g_mycount=0;
void mythread()
{
for(int i=0;i<1000000;i++)
{
// my_mutex.lock();
g_mycount++;
// my_mutex.unlock();
}
return ;
}
int main()
{
std::thread t1(mythread);
std::thread t2(mythread);
t1.join();
t2.join();
cout<<"m_mycount="<<g_mycount<<endl;
return 0;
}
在没有数据保护的情况下,两个线程同时对g_mycount进行计算,由于线程之间的上下文切换,常常会导致计算结果出错(!=2000000)。这是由于简单的加法计算,转换成低级语言时,并不是一步到位的,而是经过多步完成,假如现在存在两个线程A与线程B,当线程A正在进行加法过程中,还没有得到正确结果,此时发生了上下文切换,从而导致此次加法出错,以此类推,最终导致最后的结果出错。
18.2 原子操作
原子操作:是在多线程中不会被打断的程序执行片段;原子操作,比互斥量效率上更胜一筹;
互斥量的加锁一般是针对一个代码段(几行代码),而原子操作针对的一般都是一个变量,而不是一个代码段;
C++11 提供了一个原子类型 std::atomic<T>
,通过这个原子类型管理的内部变量就可以称之为原子变量,我们可以给原子类型指定任意的类型作为模板参数,因此原子变量也可以是任意的类型。
C++11 内置了整形的原子变量,这样就可以更方便的使用原子变量了。在多线程操作中,使用原子变量之后就不需要再使用互斥量来保护该变量了,用起来更简洁**。因为对原子变量进行的操作只能是一个原子操作(atomic operation),原子操作指的是不会被线程调度机制打断的操作,这种操作一旦开始,就一直运行到结束,中间不会有任何的上下文切换**。多线程同时访问共享资源造成数据混乱的原因就是因为 CPU 的上下文切换导致的,使用原子变量解决了这个问题,因此互斥锁的使用也就不再需要了。
#include <iostream>
#include <map>
#include <string>
#include <thread>
#include <list>
#include <mutex>
#include <future>
#include <atomic>
using namespace std;
std::mutex my_mutex;
//int g_mycount=0;
//!原子操作
std::atomic<int> g_mycount;
std::atomic<bool> g_ifend;//线程退出标记,这里是原子操作,防止写的时候出错
void mythread()
{
for(int i=0;i<1000000;i++)
{
// my_mutex.lock();
//g_mycount++;
// my_mutex.unlock();
g_mycount++; //对应的操作是原子操作,执行过程不会被打断。
}
return ;
}
void mythread2()
{
cout<<boolalpha<<g_ifend<<endl;
while (g_ifend==false)
{
//系统没要求线程退出,所以本线程可以干自己的事情
cout<<"thread id "<<std::this_thread::get_id()<<"运行中..."<<endl;
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
}
cout<<"thread id "<<std::this_thread::get_id()<<"运行结束"<<endl;
}
int main()
{
// std::thread t1(mythread);
// std::thread t2(mythread);
// t1.join();
// t2.join();
// cout<<"m_mycount="<<g_mycount<<endl;
std::thread t1(mythread2);
std::thread t2(mythread2);
std::this_thread::sleep_for(std::chrono::milliseconds(5000));
g_ifend=true;
t1.join();
t2.join();
cout<<"运行结束!"<<endl;
return 0;
}
可以用于计数或者统计(累计发出了多少个数据包,累计接受到了多少数据包)
18.3 原子操作深入
一般atomic原子操作,针对++,--,+=,&=,|=,^=
是有效的,其他操作可能导致错误,如下:
#include <iostream>
#include <map>
#include <string>
#include <thread>
#include <list>
#include <mutex>
#include <future>
#include <atomic>
using namespace std;
std::mutex my_mutex;
//int g_mycount=0;
//!原子操作
std::atomic<int> g_mycount;
void mythread()
{
for(int i=0;i<1000000;i++)
{
//!合法
// g_mycount++;
// g_mycount+=1;
//!非法
g_mycount=g_mycount+1;
}
return ;
}
int main()
{
std::thread t1(mythread);
std::thread t2(mythread);
t1.join();
t2.join();
cout<<"m_mycount="<<g_mycount<<endl;
cout<<"运行结束!"<<endl;
return 0;
}
19. Windows临界区与std::recursive_mutex(递归的独占互斥量)
19.1 Windows临界区
Windows临界区的作用与c++互斥量的作用是相似的,但是只适合在windows平台使用;
在"同一个线程中(不同线程会卡住等待)",Windows临界区变量代表临界区的进入(EnterCriticalSection(m_pCritical);)可以被多次调用,但是需要注意的是,调用了几次(EnterCriticalSection(m_pCritical);)就需要调用几次(LeaveCriticalSection(m_pCritical);)。而在c++11中,不允许“同一个线程”中lock()同一个互斥量多次,否则异常。如果确实需要在同意线程中对一个互斥量进行多次调用lock(),需要使用std::recursive_mutex(递归的独占互斥量)
#include <iostream>
#include <thread>
#include <mutex>
#include <list>
#include <Windows.h>
using namespace std;
#define _WINDOWSJQ_
//本类用于自动释放临界区,放置忘记调用LeaveCriticalSection(m_pCritical);导致死锁的发生。作用同std::lock_guard()类似;
class CWinLock
{
public:
CWinLock(CRITICAL_SECTION *pCritical)
{
m_pCritical = pCritical;
EnterCriticalSection(m_pCritical);
}
~CWinLock()
{
LeaveCriticalSection(m_pCritical);
}
private:
CRITICAL_SECTION *m_pCritical;
};
class A
{
public:
void inMsgRecvQueue()
{
for (int i = 0; i < 100000; ++i)
{
cout << "inMsgRecvQueue,执行插入一个元素" << i << endl;
#ifdef _WINDOWSJQ_
//EnterCriticalSection(&my_winsec);//进入临界区(加锁)
//msgRecvQueue.push_back(i);
//LeaveCriticalSection(&my_winsec);//退出临界区(解锁)
//使用CWinLock,相当于std::lock_guard()
CWinLock lock(&my_winsec);
msgRecvQueue.push_back(i);
#else
// _WINDOWSJQ_
//写数据时进行保护
my_mutex.lock();
msgRecvQueue.push_back(i); //保证在插入数据时,锁住程序,不被修改。
//写完后,进行解锁
my_mutex.unlock();
#endif
}
}
bool outMsgLULProc(int &command)
{
#ifdef _WINDOWSJQ_
EnterCriticalSection(&my_winsec);
if (!msgRecvQueue.empty())
{
command = msgRecvQueue.front(); //返回第一个元素
msgRecvQueue.pop_front(); //移除第一个元素
LeaveCriticalSection(&my_winsec);
return true;
}
LeaveCriticalSection(&my_winsec);
#else
my_mutex.lock();
if (!msgRecvQueue.empty())
{
command = msgRecvQueue.front(); //返回第一个元素
msgRecvQueue.pop_front(); //移除第一个元素
my_mutex.unlock();
return true;
}
my_mutex.unlock();
#endif // _WINDOWSJQ_
return false;
}
void outMsgRecvQueue()
{
int command = 0;
for (int i = 0; i < 10000; ++i)
{
bool result = outMsgLULProc(command);
if (result == true)
{
cout << "outMsgRecvQueue()执行,取出一个元素" << endl;
}
else
{
cout << "outMsgRecvQueue()为空。" << endl;
}
}
cout << "finished!" << endl;
}
A()
{
#ifdef _WINDOWSJQ_
InitializeCriticalSection(&my_winsec); //使用临界区必须要进行初始化
#endif // _WINDOWSJQ_
}
private:
list<int> msgRecvQueue;
std::mutex my_mutex;
#ifdef _WINDOWSJQ_
CRITICAL_SECTION my_winsec; //windows中的临界区,与c++中mutex非常类似
#endif
};
int main()
{
A myobja;
std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myOutMsgObj.join();
myInMsgObj.join();
return 0;
}
19.2 std::recursive_mutex(递归的独占互斥量)
std::mutex:独占互斥量,自己lock时,别人不能lock;
std::recursive_mutex:递归的独占互斥量:允许同一个线程,同一个互斥量多次被lock();
std::recursive_mutex与std::mutex相比,效率上更低一点。
//std::mutex my_mutex1;
std::recursive_mutex;//尽量少使用recursive_mutex;
20.带超时的互斥量std::timed_mutex 和 std::recursive_timed_mutex
std::timed_mutex:带超时的互斥量独占锁:**防止互斥量一直在等待!**比如一个线程正在长时间占用锁,若采用互斥量独占互斥量,那么想要拿到锁,需要等待其他线程执行完毕,采用带超时的互斥量的独占锁,可以避免这一点。与lock_guard()中的trylock()相似;
- try_lock_for():参数是一个一段时间,等待一段时间。如果在这段时间中拿到了锁,就会继续执行,如果超时这段时间没有拿到锁,就会执行其他语句比如(if …else…)。
- try_lock_until():参数是一个未来时间点,在这个时间点之前,如果拿到了锁,那么就会走下下来,如果时间没有到,也会走下来
std::recursive_timed_mutex:递归带超时的互斥量独占锁
#include <iostream>
#include <thread>
#include <mutex>
#include <list>
using namespace std;
class A
{
public:
void inMsgRecvQueue()
{
for (int i = 0; i < 100000; ++i)
{
cout << "inMsgRecvQueue,执行插入一个元素" << i << endl;
std::chrono::milliseconds timeout(100);
//!try_lock_for()
//if (my_mutex.try_lock_for(timeout)) //!等待100ms来尝试获取锁
//!try_lock_for()
if(my_mutex.try_lock_until(chrono::steady_clock::now()+timeout))//!等待100ms来尝试获取锁
{
//拿到锁
msgRecvQueue.push_back(i); //保证在插入数据时,锁住程序,不被修改。
my_mutex.unlock();//!写完后,进行解锁
}
else
{
//没拿到锁
std::this_thread::sleep_for(200ms);
cout<<"此次没有拿到锁,等待200ms"<<endl;
}
}
}
bool outMsgLULProc(int &command)
{
my_mutex.lock();
if (!msgRecvQueue.empty())
{
command = msgRecvQueue.front(); //返回第一个元素
msgRecvQueue.pop_front(); //移除第一个元素
my_mutex.unlock();
return true;
}
my_mutex.unlock();
return false;
}
void outMsgRecvQueue()
{
int command = 0;
for (int i = 0; i < 10000; ++i)
{
bool result = outMsgLULProc(command);
if (result == true)
{
cout << "outMsgRecvQueue()执行,取出一个元素" << endl;
}
else
{
cout << "outMsgRecvQueue()为空。" << endl;
}
}
cout << "finished!" << endl;
}
private:
list<int> msgRecvQueue;
//std::mutex my_mutex;
std::timed_mutex my_mutex; //带超时功能的互斥量
};
int main()
{
A myobja;
std::thread myOutMsgObj(&A::outMsgRecvQueue, &myobja);
std::thread myInMsgObj(&A::inMsgRecvQueue, &myobja);
myOutMsgObj.join();
myInMsgObj.join();
return 0;
}
21.线程池
最后
以上就是玩命香烟为你收集整理的多线程Cpp的全部内容,希望文章能够帮你解决多线程Cpp所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复