对应视频:c++11并发与多线程视频课程
#include
#include
using namespace std;
//自己创建的线程需要从一个函数(初始函数)开始运行
void myPrint() {
cout << "myPrint" << endl;
}
int main() {
//程序运行起来,生成一个进程,该进程所属的主线程开始自动运行;当主线程从main()函数返回,则整个进程执行完毕
//主线程从main()开始执行,那么我们自己创建的线程,也需要从一个函数开始运行(初始函数),一旦这个函数运行完毕,线程也结束运行
//整个进程是否执行完毕的标志是:主线程是否执行完,如果主线程执行完毕了,就代表整个进程执行完毕了,此时如果其他子线程还没有执行完,也会被强行终止
//thread是C++11的线程类,myprint是可调用对象
thread mytobj(myPrint); //创建了线程,线程执行起点是myPrint();然后myPrint()开始执行
//阻塞主线程(main函数),让主线程等待子线程执行完毕,然后让子线程和主线程汇合,然后主线程再往下走。
mytobj.join();//如果不加这行可能导致子线程还没执行完主线程就执行完了,会产生报错
cout << "main" << endl;
//一个良好的程序应该是主线程等待子线程执行完毕后,自己才能最终退出
return 0;
}
void myPrint() {
cout << "1" << endl;
cout << "2" << endl;
cout << "3" << endl;
cout << "4" << endl;
cout << "5" << endl;
cout << "6" << endl;
}
int main() {
thread mytobj(myPrint);
//与主线程分离,主线程不用再等待子线程运行结束
//一旦detach()以后,主线程就和子线程失去关联,此时子线程就会驻留在后台运行
//这个子线程相当于被C++运行时库接管,当这个子线程执行完成后,由运行时库负责清理该线程的资源
//detach()会使线程失去我们的控制
//一旦调用了detach(),就不能再用join()
mytobj.detach();
cout << "main" << endl;
return 0;
}
void myPrint() {
cout << "myPrint" << endl;
}
int main() {
thread mytobj(myPrint);
//joinable() 判断是否可以成功使用join()或者detach()
cout << mytobj.joinable() << endl;
mytobj.join();
cout << mytobj.joinable() << endl;
cout << "main" << endl;
return 0;
}
class TA {
public:
void operator()() {
cout << "TA" << endl;
}
};
int main() {
TA ta;
thread mytobj(ta);//ta可调用对象
mytobj.join();
cout << "main" << endl;
return 0;
}
int main() {
auto myLambda = [] {
cout << "1" << endl;
};
thread mytobj(myLambda);
mytobj.join();
cout << "main" << endl;
return 0;
}
class A {
public:
int m_i;
A(int a) :m_i(a) { }
A(const A &a) :m_i(a.m_i) { }
~A() { }
//线程执行入口
void thread_work(int num) {
cout << "thread_work" << endl;
}
};
int main() {
A myobj(10);
thread mytobj(&A::thread_work, &myobj,15);//对象要用&或者std::ref(),不然会复制出一个新对象
mytobj.join();
return 0;
}
void myprint(int i, int j) {
cout << i << endl;
cout << j << endl;
}
int main() {
thread mytojb(myprint, 1, 2);//先是可调用对象,然后依次是函数参数
mytojb.join();
cout << "main" << endl;
return 0;
}
指针指向的空间随着主线程的运行结束可能被销毁
void myprint(const int &i, char *pmybuf) {
//这里i并不是真正的引用,实际是值传递
//即使主线程detach了子线程,子线程中用i值仍然是安全的
cout << i << endl;
//指针绝对有问题,指向同样的内存空间
cout << pmybuf << endl;
}
int main() {
int mvar = 1;
char mybuf[] = "this is a test!";
thread mytojb(myprint, mvar, mybuf);//myprint的参数这里是值传递
mytojb.detach();
cout << "main" << endl;
return 0;
}
改用string以后,可能存在隐式转换在主线程运行结束以后
void myprint(const int &i, const string &pmybuf) {
cout << i << endl;
cout << pmybuf.c_str() << endl;
}
int main() {
int mvar = 1;
char mybuf[] = "this is a test!";
//存在问题:mubuf到底在什么时候隐式转成string
//事实上存在 “mybuf都被回收了(main函数执行完了),系统才用mybuf去转string” 的情况
thread mytojb(myprint, mvar, mybuf);
mytojb.detach();
cout << "main" << endl;
return 0;
}
void myprint(const int &i, const string &pmybuf) {
cout << i << endl;
cout << pmybuf.c_str() << endl;
}
int main() {
int mvar = 1;
char mybuf[] = "this is a test!";
//在创建线程的同时构造临时对象的方法传递参数是可行的
thread mytojb(myprint, mvar, string(mybuf));//直接将mybuf转成string对象,这样就可以保证在线程中用肯定有效的对象
mytojb.detach();
cout << "main" << endl;
return 0;
}
线程id概念
class A {
public:
int m_i;
A(int a) :m_i(a) { cout << "构造函数执行" << this << " threadid=" << this_thread::get_id() << endl; }
A(const A &a) :m_i(a.m_i) { cout << "拷贝构造函数执行" << this << " threadid=" << this_thread::get_id() << endl; }
~A(){ cout << "析构函数执行" << this << " threadid=" << this_thread::get_id() << endl; }
};
void myprint(const A &a) {
cout << "子线程a的参数地址是" << &a << " threadid=" << this_thread::get_id() << endl;
}
int main() {
cout << "主线程id" << this_thread::get_id() << endl;
int mvar = 1;
thread mytobj(myprint, mvar);
mytobj.join();
cout << "main" << endl;
return 0;
}
运行结果
主线程id11880
构造函数执行0198F9BC threadid=14772
子线程a的参数地址是0198F9BC threadid=14772
析构函数执行0198F9BC threadid=14772
main
从运行结果中可以看出,隐式类型转换是在子线程中构造A类对象
class A {
public:
int m_i;
A(int a) :m_i(a) { cout << "构造函数执行" << this << " threadid=" << this_thread::get_id() << endl; }
A(const A &a) :m_i(a.m_i) { cout << "拷贝构造函数执行" << this << " threadid=" << this_thread::get_id() << endl; }
~A(){ cout << "析构函数执行" << this << " threadid=" << this_thread::get_id() << endl; }
};
void myprint(const A &a) {
cout << "子线程a的参数地址是" << &a << " threadid=" << this_thread::get_id() << endl;
}
int main() {
cout << "主线程id" << this_thread::get_id() << endl;
int mvar = 1;
thread mytobj(myprint, A(mvar));
mytobj.join();
cout << "main" << endl;
return 0;
}
运行结果
主线程id10568
构造函数执行00AFF968 threadid=10568
拷贝构造函数执行00D4FBC0 threadid=10568
子线程a的参数地址是00D4FBC0 threadid=5196
析构函数执行00AFF968 threadid=10568
析构函数执行00D4FBC0 threadid=5196
main
从运行结果中可以看出,用了临时对象后,所有的A类对象都在main()函数中就已经构建完毕了
void myprint(int &a) {
cout << a << endl;
a = 100;
}
int main() {
int a = 1;
thread mytobj(myprint, ref(a));
mytobj.join();
cout << a << endl;
return 0;
}
void myprint(unique_ptr<int> p) {
cout << *p << endl;
}
int main() {
unique_ptr<int> p(new int(100));//独占式智能指针
thread mytobj(myprint, move(p));//独占式智能指针不能拷贝,只能移动
mytobj.join();
return 0;
}
多个线程的执行顺序是乱的,跟操作系统内部对线程的运行调度机制有关。
//线程入口函数
void myprint(int inum) {
cout << "myprint线程开始执行," << " 线程编号=" << inum << endl;
cout << "myprint线程结束执行," << " 线程编号=" << inum << endl;
}
int main() {
vector<thread> mythreads;//把thread对象放入到容器里管理,方便对线程的管理
//创建10个线程,线程入口函数统一使用myprint
for (int i = 0; i < 10; i++) {
mythreads.push_back(thread(myprint, i));//创建并开始执行线程
}
for (auto iter = mythreads.begin(); iter != mythreads.end(); iter++) {
iter->join();//等待10个线程都返回
}
cout << "main" << endl;
return 0;
}
只读的数据,是安全稳定的,不需要特别的处理,直接读就可以
vector<int> g_v = { 1,2,3 };//共享数据(只读)
//线程入口函数
void myprint(int inum) {
cout << "id为" << this_thread::get_id() << "的线程打印g_v的值" << g_v[0] << g_v[1] << g_v[2] << endl;
}
int main() {
vector<thread> mythreads;//把thread对象放入到容器里管理,方便对线程的管理
//创建10个线程,线程入口函数统一使用myprint
for (int i = 0; i < 10; i++) {
mythreads.push_back(thread(myprint, i));//创建并开始执行线程
}
for (auto iter = mythreads.begin(); iter != mythreads.end(); iter++) {
iter->join();//等待10个线程都返回
}
cout << "main" << endl;
return 0;
}
会出问题,会报错,需要使用锁(见下一章)
以下代码不使用锁,进程同时读写共享数据时会出问题
class A {
public:
//收到的消息放入到一个容器中
void inMsgRecvQueue() {
for (int i = 0; i < 10000; i++) {
cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
msgRecvQueue.push_back(i);//假设数字i就是收到的消息,放入消息队列中
}
}
//把消息从容器中取出
void outMsgRecvQueue() {
for (int i = 0; i < 10000; i++) {
if (!msgRecvQueue.empty()) {
//消息队列不为空
int msg = msgRecvQueue.front();
msgRecvQueue.pop_front();
//处理消息......
}
else {
//消息队列为空
cout << "outMsgRecvQueue()执行,但目前消息队列中为空" << i << endl;
}
}
cout << "end" << endl;
}
private:
list<int> msgRecvQueue;//专门用于存收到的消息的容器(消息队列)
};
int main() {
A myobj;
thread myOutMsgObj(&A::outMsgRecvQueue, &myobj);//第二个参数加&保证在线程中用的是同一个对象
thread myInMsgObj(&A::inMsgRecvQueue, &myobj);
myOutMsgObj.join();
myInMsgObj.join();
cout << "main" << endl;
return 0;
}
互斥量(mutex)是个类对象,同一时刻多个线程都可以调用互斥量的成员函数lock()来尝试加锁,只有一个线程可以锁成功,成功的标志是lock()函数返回了;如果没锁成功,那么线程会卡在lock()不断地去尝试加锁。
unlock()用于解锁。
lock()和unlock()要成对使用。
class A {
public:
//收到的消息放入到一个容器中
void inMsgRecvQueue() {
for (int i = 0; i < 10000; i++) {
cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
my_mutex.lock();
msgRecvQueue.push_back(i);//假设数字i就是收到的消息,放入消息队列中
my_mutex.unlock();
}
}
bool outMsg(int &msg) {
my_mutex.lock();
if (!msgRecvQueue.empty()) {
//消息队列不为空
msg = msgRecvQueue.front();
msgRecvQueue.pop_front();
my_mutex.unlock();
return true;
}
my_mutex.unlock();
return false;
}
//把消息从容器中取出
void outMsgRecvQueue() {
int msg = 0;
for (int i = 0; i < 10000; i++) {
bool result = outMsg(msg);
if (result) {
cout << "outMsgRecvQueue()执行,取出一个元素 " << msg << endl;
//处理消息...
}
else {
//消息队列为空
cout << "outMsgRecvQueue()执行,但目前消息队列中为空" << i << endl;
}
}
cout << "end" << endl;
}
private:
list<int> msgRecvQueue;//专门用于存收到的消息的容器(消息队列)
mutex my_mutex;//互斥量
};
int main() {
A myobj;
thread myOutMsgObj(&A::outMsgRecvQueue, &myobj);//第二个参数加&保证在线程中用的是同一个对象
thread myInMsgObj(&A::inMsgRecvQueue, &myobj);
myOutMsgObj.join();
myInMsgObj.join();
cout << "main" << endl;
return 0;
}
可以直接取代lock()和unlock(),具有创建时加锁,析构时解锁的功能(无需手动解锁)
用了lock_guard以后,就不能使用lock()和unlock()
lock_guard原理是构造函数里执行了lock(),析构函数里执行了unlock()
class A {
public:
//收到的消息放入到一个容器中
void inMsgRecvQueue() {
for (int i = 0; i < 10000; i++) {
cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
my_mutex.lock();
msgRecvQueue.push_back(i);//假设数字i就是收到的消息,放入消息队列中
my_mutex.unlock();
}
}
bool outMsg(int &msg) {
lock_guard<mutex> guard(my_mutex);
if (!msgRecvQueue.empty()) {
//消息队列不为空
msg = msgRecvQueue.front();
msgRecvQueue.pop_front();
return true;
}
return false;
}
//把消息从容器中取出
void outMsgRecvQueue() {
int msg = 0;
for (int i = 0; i < 10000; i++) {
bool result = outMsg(msg);
if (result) {
cout << "outMsgRecvQueue()执行,取出一个元素 " << msg << endl;
//处理消息...
}
else {
//消息队列为空
cout << "outMsgRecvQueue()执行,但目前消息队列中为空" << i << endl;
}
}
cout << "end" << endl;
}
private:
list<int> msgRecvQueue;//专门用于存收到的消息的容器(消息队列)
mutex my_mutex;//互斥量
};
int main() {
A myobj;
thread myOutMsgObj(&A::outMsgRecvQueue, &myobj);//第二个参数加&保证在线程中用的是同一个对象
thread myInMsgObj(&A::inMsgRecvQueue, &myobj);
myOutMsgObj.join();
myInMsgObj.join();
cout << "main" << endl;
return 0;
}
mutex mutex1;
mutex mutex2;
//std::lock() 函数可接受任意数量的互斥量, 该函数要么对所有参数互斥量都成功上锁,要么一个也不上锁。
//可在一定程度上避免死锁
lock(mutex1, mutex2);//同时锁mutex1和mutex2
mutex1.unlock();
mutex2.unlock();
std::adopt_lock
是个结构体对象,起一个标记作用。
作用就表示这个互斥量已经lock(),不需要在std::lock_guard
的构造函数里对mutex类对象再lock()了。
mutex mutex1;
mutex mutex2;
lock(mutex1, mutex2);//同时锁mutex1和mutex2
lock_guard<mutex> guard1(mutex1, adopt_lock);
lock_guard<mutex> guard2(mutex2, adopt_lock);
//mutex1.unlock();
//mutex2.unlock();
unique_lock是个类模板,对互斥量进行加锁和解锁管理。
unique_lock比lock_guard灵活很多,但是效率差一点,内存占用多一点。
第二个参数缺省情况下,unique_lock和lock_guard一样
class A {
public:
//收到的消息放入到一个容器中
void inMsgRecvQueue() {
for (int i = 0; i < 10000; i++) {
cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
unique_lock<mutex> u(my_mutex);
msgRecvQueue.push_back(i);//假设数字i就是收到的消息,放入消息队列中
}
}
bool outMsg(int &msg) {
unique_lock<mutex> u(my_mutex);
if (!msgRecvQueue.empty()) {
//消息队列不为空
msg = msgRecvQueue.front();
msgRecvQueue.pop_front();
return true;
}
return false;
}
//把消息从容器中取出
void outMsgRecvQueue() {
int msg = 0;
for (int i = 0; i < 10000; i++) {
bool result = outMsg(msg);
if (result) {
cout << "outMsgRecvQueue()执行,取出一个元素 " << msg << endl;
//处理消息...
}
else {
//消息队列为空
cout << "outMsgRecvQueue()执行,但目前消息队列中为空" << i << endl;
}
}
cout << "end" << endl;
}
private:
list<int> msgRecvQueue;//专门用于存收到的消息的容器(消息队列)
mutex my_mutex;//互斥量
};
int main() {
A myobj;
thread myOutMsgObj(&A::outMsgRecvQueue, &myobj);//第二个参数加&保证在线程中用的是同一个对象
thread myInMsgObj(&A::inMsgRecvQueue, &myobj);
myOutMsgObj.join();
myInMsgObj.join();
cout << "main" << endl;
return 0;
}
std::adopt_lock是一个标记,表示这个互斥量已经lock,不用在构造函数中lock。
class A {
public:
//收到的消息放入到一个容器中
void inMsgRecvQueue() {
for (int i = 0; i < 10000; i++) {
cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
my_mutex.lock();//提前加锁
unique_lock<mutex> u(my_mutex,adopt_lock);
msgRecvQueue.push_back(i);//假设数字i就是收到的消息,放入消息队列中
}
}
bool outMsg(int &msg) {
my_mutex.lock();
unique_lock<mutex> u(my_mutex,adopt_lock);
if (!msgRecvQueue.empty()) {
//消息队列不为空
msg = msgRecvQueue.front();
msgRecvQueue.pop_front();
return true;
}
return false;
}
//把消息从容器中取出
void outMsgRecvQueue() {
int msg = 0;
for (int i = 0; i < 10000; i++) {
bool result = outMsg(msg);
if (result) {
cout << "outMsgRecvQueue()执行,取出一个元素 " << msg << endl;
//处理消息...
}
else {
//消息队列为空
cout << "outMsgRecvQueue()执行,但目前消息队列中为空" << i << endl;
}
}
cout << "end" << endl;
}
private:
list<int> msgRecvQueue;//专门用于存收到的消息的容器(消息队列)
mutex my_mutex;//互斥量
};
int main() {
A myobj;
thread myOutMsgObj(&A::outMsgRecvQueue, &myobj);//第二个参数加&保证在线程中用的是同一个对象
thread myInMsgObj(&A::inMsgRecvQueue, &myobj);
myOutMsgObj.join();
myInMsgObj.join();
cout << "main" << endl;
return 0;
}
使用try_to_lock会尝试用mutex的lock()去锁定这个mutex,但如果没有锁定成功,也会立即返回,并不会阻塞在那里。
用try_to_lock的前提是不能先去lock互斥量。
class A {
public:
//收到的消息放入到一个容器中
void inMsgRecvQueue() {
for (int i = 0; i < 10000; i++) {
unique_lock<mutex> u(my_mutex,try_to_lock);
if (u.owns_lock()) {
//拿到了锁,操作共享数据
cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
msgRecvQueue.push_back(i);
}
else {
//没有拿到锁
cout << "inMsgRecvQueue()没拿到锁" << endl;
}
}
}
bool outMsg(int &msg) {
unique_lock<mutex> u(my_mutex);
chrono::milliseconds dura(200);
this_thread::sleep_for(dura);//休息200毫秒
if (!msgRecvQueue.empty()) {
//消息队列不为空
msg = msgRecvQueue.front();
msgRecvQueue.pop_front();
return true;
}
return false;
}
//把消息从容器中取出
void outMsgRecvQueue() {
int msg = 0;
for (int i = 0; i < 10000; i++) {
bool result = outMsg(msg);
if (result) {
cout << "outMsgRecvQueue()执行,取出一个元素 " << msg << endl;
//处理消息...
}
else {
//消息队列为空
cout << "outMsgRecvQueue()执行,但目前消息队列中为空" << i << endl;
}
}
cout << "end" << endl;
}
private:
list<int> msgRecvQueue;//专门用于存收到的消息的容器(消息队列)
mutex my_mutex;//互斥量
};
int main() {
A myobj;
thread myOutMsgObj(&A::outMsgRecvQueue, &myobj);//第二个参数加&保证在线程中用的是同一个对象
thread myInMsgObj(&A::inMsgRecvQueue, &myobj);
myOutMsgObj.join();
myInMsgObj.join();
cout << "main" << endl;
return 0;
}
defer_lock意思是构造函数的时候不需要给mutex加锁。
class A {
public:
//收到的消息放入到一个容器中
void inMsgRecvQueue() {
for (int i = 0; i < 10000; i++) {
cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
unique_lock<mutex> u(my_mutex, defer_lock);//没有对my_mutex加锁
u.lock();//加锁,不用关心解锁
msgRecvQueue.push_back(i);
}
}
bool outMsg(int &msg) {
unique_lock<mutex> u(my_mutex);
if (!msgRecvQueue.empty()) {
//消息队列不为空
msg = msgRecvQueue.front();
msgRecvQueue.pop_front();
return true;
}
return false;
}
//把消息从容器中取出
void outMsgRecvQueue() {
int msg = 0;
for (int i = 0; i < 10000; i++) {
bool result = outMsg(msg);
if (result) {
cout << "outMsgRecvQueue()执行,取出一个元素 " << msg << endl;
//处理消息...
}
else {
//消息队列为空
cout << "outMsgRecvQueue()执行,但目前消息队列中为空" << i << endl;
}
}
cout << "end" << endl;
}
private:
list<int> msgRecvQueue;//专门用于存收到的消息的容器(消息队列)
mutex my_mutex;//互斥量
};
int main() {
A myobj;
thread myOutMsgObj(&A::outMsgRecvQueue, &myobj);//第二个参数加&保证在线程中用的是同一个对象
thread myInMsgObj(&A::inMsgRecvQueue, &myobj);
myOutMsgObj.join();
myInMsgObj.join();
cout << "main" << endl;
return 0;
}
给绑定的互斥量加锁。
给绑定的互斥量解锁。
可以手动unlock(),unique_lock在析构的时候会判断对应的mutex有没有unlock,如果已经unlock,就不会再次unlock
尝试给互斥量加锁,如果拿不到锁,则返回false,如果拿到了锁,则返回true,这个函数不会阻塞。
返回它所管理的mutex对象指针,并释放所有权。
unique_lock和mutex不再有关系。
如果原来mutex处于加锁状态,断开关系以后需要手动解锁。
unique_lockmyUniLock(myMutex);把myMutex和myUniLock绑定在了一起,也就是myUniLock拥有myMutex的所有权
unique_lock<mutex> aFunction()
{
unique_lock<mutex> myUniLock(myMutex);
//返回这种局部对象会导致系统生成临时的unique_lock对象,并调用unique_lock的移动构造函数
return myUniLock;
}
// 然后就可以在外层调用,在sbguard具有对myMutex的所有权
std::unique_lock<std::mutex> sbguard = aFunction();