1. 基本概念
1.1 并发
并发:多个任务同时进行;一个程序同时执行多个独立任务
以往的单核CPU ,同一时刻只能先执行一个任务。由于操作系统的调度,每秒进行多次的任务切换;这不是真正的并发,这种上写文切换需要时间开销的;
硬件发展现在出现了多核CPU计算机,能够真正并行执行多个任务;
1.2 可执行程序
windows下:.exe磁盘下的一个文件
1.3 进程和线程
可执行程序运行起来就是一个进程
每个进程都有一个唯一的一个主线程,进程与主线程同时存在,同时消亡
线程,就是用来执行代码的,
线程这个东西,理解成一条代码的执行通路
除了主线程外,我们可以通过代码创建别的进程,其他线程走的是别的通路,甚至到了别的地方
每创建一个新线程,就可以在同一时刻多干一个不同的事;
多线程就是并发
线程不是越多越好,每个线程,都需要一个独立的堆栈空间(1 M),线程之间的切换要保存很多中间状态,切会耗费时间
1.4 并发的实现方法
通过多个进程实现并发
进程之间通信
同一电脑上:管道、文件、消息队列、共享内存
不同电脑上:socket通信
通过多个线程实现并发——单一进程内
同一进程内的多线程共享内存,也带来问题:数据一致性问题
1.5 C++11新标准
C++语言本身增加对多线程的支持,跨平台,windows/linux都可以;
2. 多线程代码演示及解释
2.1 创建线程、join、detach
thread类利用可调用对象创建线程:
① 函数
② 类
必须重载()运算符才能作为可调用对象
#include "stdafx.h"
#include<iostream>
#include <thread>
using namespace std;
class Ta
{
public:
void operator()()//不能带参数
{
cout << "我的线程operator()开始执行" << endl;
cout << "我的线程operator()结束执行" << endl;
}
};
void myjob()
{
cout << "我的线程开始执行" << endl;
cout << "我的线程结束执行" << endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
thread m_thread1(myjob);
//创建线程,
//thread接收可调用对象作为参数来创造线程
//myjob是函数
//这条语句执行以后,线程即创建并开始执行,线程的执行入口是myjob
m_thread1.join();
//join()表示阻塞,阻塞主线程,等待子线程执行结束,主线程再往下走
m_thread.detach();
//detach()表示分离子线程,则子线程和主线程没有任何关系了
//补充:join和detach不能同时使用
Ta ta;
thread m_thread2(ta);
m_thread2.join();
cout << "我的进程开始执行" << endl;
return 0;
}
补充说明:
利用detach()分离线程以后,则子线程的执行不受主线程控制
问题说明:
假如使用类的对象作为线程的可调用对象,并利用detach()分离线程,当主线程执行结束后,ta这个对象还存在么?
解释:ta肯定不存在了!!!但是,ta这个对象不存在不影响子线程的执行,因为ta是被复制到子线程中去的,即:又生成一个对象,进入子线程。
2.2 线程传参详解
传递类的临时对象作为线程参数
#include "stdafx.h"
#include<iostream>
#include <thread>
using namespace std;
void myjob(const int &i, char *mybuf)
{
cout << i << endl;
cout << mybuf << endl;
return;
}
int _tmain(int argc, _TCHAR* argv[])
{
//传递临时对象作为线程参数
int mi = 1;
int &myi = mi;
char mybuf[] = "This is path";
thread m_thread(myjob,mi,mybuf);
//传可调用对象的同时,传递函数参数
m_thread.join();
cout << "我的进程开始执行" << endl;
return 0;
}
假如使用detach(),主程序执行结束,子程序还未结束,那子程序中使用的临时对象是安全的么???
解释说明:
在主程序中,myi是mi的引用,二者的地址是相同的;
但是,利用myjob进行线程创造时,将mi传递进去,myjob中本来是 i 的引用,其地址应该和mi地址相同,但实际执行时不是。实际上是值传递;
将join()改成detach()后,假如子程序未执行完,主程序已经结束,在子程序中 i 仍然时安全的,但是mybuf是不安全的。因为它和主程序中用的是同一块地址,主程序结束后,mybuf被系统回收,则不安全。
所以,detach()的子线程一定不能用指针,最好不要用引用;
解决方法:
#include "stdafx.h"
#include<iostream>
#include <thread>
using namespace std;
class A
{
public:
int m_i;
A(int i) :m_i(i)
{
cout << "构造函数" << endl;
}
A(const A &a) :m_i(a.m_i)
{
cout << "拷贝构造函数" << endl;
}
~A()
{
cout << "析构函数" << endl;
}
};
void myjob(const int i, const A a)
{
cout << i << endl;
cout << a.m_i << endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
//传递临时对象作为线程参数
int mi = 1;
int i = 2;
//thread m_thread(myjob,mi,i);//希望利用i构造对象传递给myjob,写成这样不行
//实际上是main函数执行完以后才进行线程中用到的A类对象的构造
thread m_thread(myjob, mi, A(i));//用一个临时对象构造,这样就可以了
//m_thread.join();
m_thread.detach();
cout << "我的进程开始执行" << endl;
return 0;
}
终极结论
① 若传递int这种简单数据类型,建议采用值传递,不要用引用
② 如果传递类对象,避免隐式类型转换,全部在创建线程这一行就构建出临时对象来,然后在函数里,用引用来接
终极意见
建议不适用detach(),只是用join
线程ID:每个线程都有一个唯一的一个ID,即一组数字,可以用std::this_thread::get_id()
来获取
如果不构造临时对象进行传参,对象是在子线程中进行构造;
如果构造临时对象进行传参,对象是在主线程中进行构造;
可以通过线程ID进行查看验证
传递类对象作为线程参数
传递类的成员函数指针做线程参数
A m_a(10);//创建类的对象
std::thread m_thread(&A::thread_work,m_a,15);
//创建线程,类成员函数指针,类的对象,成员函数参数
m_thread.join();
传递类的成员函数指针做线程参数,线程创建时第一个参数是类的成员函数指针,第二个参数是类的对象引用,从第三个参数开始才可以给类的这个成员函数传递实参。
而如果线程的入口函数是普通函数时,从第二个参数开始就可以给这个函数传递实参。
3. 数据共享
3.1 创建和等待多个线程
#include "stdafx.h"
#include<iostream>
#include <thread>
#include<vector>
using namespace std;
void myjob(int i)
{
cout << "进程"<<this_thread::get_id()<< endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
vector<thread> mythreads;
for (int i = 0; i < 10; i++)
{
mythreads.push_back(thread(&myjob,i));
}
for (auto iter = mythreads.begin(); iter != mythreads.end(); ++iter)
{
iter->join();
}
cout << "我的进程开始执行" << endl;
return 0;
}
补充:
由于线程的上下文调度,这10个线程的执行实际上是不可控的;即:彼此输出结果时,可能输出到一半,就被系统分配去执行另一个线程了;
但是由于在后面分别调用了join(),所以主线程一定会等到所有的子线程执行结束再执行。
3.2 共享数据
① 如果是只读数据,那么共享是安全的。
② 有读有写
最简单的不崩溃处理:读时不能写,写时不能读,不能同时写,不能同时读
代码举例:
最简单的网络游戏服务器,两个线程,一个线程收集玩家命令,并把命令数据写到一个数据队列中;另一个线程,从队列去除玩家发送的命令,解析,执行;
#include "stdafx.h"
#include<iostream>
#include <thread>
#include<vector>
#include<list>
using namespace std;
class A
{
public:
//收集数据线程,相当于写
void inMsgResvQueue()
{
for (int i = 0; i < 10000; i++)//设置10000防止程序一下就执行完
{
cout << "inMsgResvQueue()执行,插入一个元素" << endl;
msgRecvQueue.push_back(i);
}
}
//取出数据线程,相当于读
void outMsgResvQueue()
{
for (int i = 0; i < 10000; i++)//设置10000防止程序一下就执行完
{
if (!msgRecvQueue.empty())
{
int command = msgRecvQueue.front();//返回第一个元素
msgRecvQueue.pop_front();//删除第一个元素
//进行数据处理
}
else
{
cout << "outMsgResvQueue()执行,但消息队列为空" << i<<endl;
}
}
}
private:
list<int> msgRecvQueue;//容器,收集命令
};
int _tmain(int argc, _TCHAR* argv[])
{
A a;
thread myoutMsgThread(&A::outMsgResvQueue,&a);//第二个参数是引用,才能保证线程里用的是同一个对象
thread myinMsgThread(&A::inMsgResvQueue, &a);
myoutMsgThread.join();
myinMsgThread.join();
cout << "我的进程开始执行" << endl;
return 0;
}
执行结果:
崩溃说明:
此时程序有读有写,某一时刻同时读写,会出错;
解决方法:
操作时,用代码把共享数据锁住,操作数据,解锁;
其他想操作共享的数据必须等待解锁,然后上锁,操作,解锁
互斥量mutex
互斥量相当于一把锁,多个线程尝试用lock()成员函数加锁这个锁头,但是同一时刻只有一个线程可以枷锁成功;如果没锁成功,那么该进程卡在lock()这里不断的尝试加锁。
互斥量保护多了,影响代码执行效率;保护少了,达不到保护效果。所以互斥量仅仅对实际操作共享数据时进行锁
注意:lock()和unlock()必须成对使用
#include "stdafx.h"
#include<iostream>
#include <thread>
#include<vector>
#include<list>
#include<mutex>
using namespace std;
class A
{
public:
//收集数据线程,相当于写
void inMsgResvQueue()
{
for (int i = 0; i < 1000; i++)//设置1000防止程序一下就执行完
{
cout << "inMsgResvQueue()执行,插入一个元素" << endl;
m_mutex.lock();
msgRecvQueue.push_back(i);
m_mutex.unlock();
}
}
bool outMsg(int &command)
{
m_mutex.lock();
if (!msgRecvQueue.empty())//这个也是对消息队列的操作,也要放在锁里面
{
int command = msgRecvQueue.front();//返回第一个元素
msgRecvQueue.pop_front();//删除第一个元素
m_mutex.unlock();//成对使用
return true;
}
m_mutex.unlock();//成对使用
return false;
}
//取出数据线程,相当于读
void outMsgResvQueue()
{
int command = 0;
for (int i = 0; i < 1000; i++)//设置1000防止程序一下就执行完
{
bool result = outMsg(command);
if (result=true)
{
cout << "outMsgResvQueue()执行,取出一个元素" << i << endl;
}
else
{
cout << "outMsgResvQueue()执行,但消息队列为空" << i<<endl;
}
}
}
private:
list<int> msgRecvQueue;//容器,收集命令
mutex m_mutex;//互斥量
};
int _tmain(int argc, _TCHAR* argv[])
{
A a;
thread myoutMsgThread(&A::outMsgResvQueue,&a);//第二个参数是引用,才能保证线程里用的是同一个对象
thread myinMsgThread(&A::inMsgResvQueue, &a);
myoutMsgThread.join();
myinMsgThread.join();
//创建两个线程,这两个线程阻塞主线程,分别执行
//在线程内,利用互斥量mutex实现共享数据保护
cout << "我的进程开始执行" << endl;
return 0;
}
为了防止lock()和unlock()遗漏问题,使用lock_guard类模板,就是说:是使用lock_guard以后,不能再使用lock和unlock
#include "stdafx.h"
#include<iostream>
#include <thread>
#include<vector>
#include<list>
#include<mutex>
using namespace std;
class A
{
public:
//收集数据线程,相当于写
void inMsgResvQueue()
{
for (int i = 0; i < 1000; i++)//设置1000防止程序一下就执行完
{
cout << "inMsgResvQueue()执行,插入一个元素" << endl;
lock_guard<mutex> m_guard(m_mutex);
msgRecvQueue.push_back(i);
}
}
bool outMsg(int &command)
{
lock_guard<mutex> m_guard(m_mutex);
//lock_guard类模板的构造函数中相当于执行了lock()
//lock_guard类模板的析构函数中相当于执行了unlock()
if (!msgRecvQueue.empty())//这个也是对消息队列的操作,也要放在锁里面
{
int command = msgRecvQueue.front();//返回第一个元素
msgRecvQueue.pop_front();//删除第一个元素
return true;
}
return false;
}
//取出数据线程,相当于读
void outMsgResvQueue()
{
int command = 0;
for (int i = 0; i < 1000; i++)//设置1000防止程序一下就执行完
{
bool result = outMsg(command);
if (result=true)
{
cout << "outMsgResvQueue()执行,取出一个元素" << i << endl;
}
else
{
cout << "outMsgResvQueue()执行,但消息队列为空" << i<<endl;
}
}
}
private:
list<int> msgRecvQueue;//容器,收集命令
mutex m_mutex;
};
int _tmain(int argc, _TCHAR* argv[])
{
A a;
thread myoutMsgThread(&A::outMsgResvQueue,&a);//第二个参数是引用,才能保证线程里用的是同一个对象
thread myinMsgThread(&A::inMsgResvQueue, &a);
myoutMsgThread.join();
myinMsgThread.join();
//创建两个线程,这两个线程阻塞主线程,分别执行
//在线程内,利用互斥量mutex实现共享数据保护
cout << "我的进程开始执行" << endl;
return 0;
}
相比于lock和unlock,lock_guard更安全,但是不如前者灵活,前者可以随时加锁解锁,但是后者必须在对象析构的时候才能解锁。
还有unique_lock,如何使用自行查阅视频资料
3.3 死锁
概念模型
一个互斥量相当于一把锁,死锁发生的前提至少需要两把锁,即两个互斥量
假设如下情况:
现在有两把锁:金锁、银锁,两个线程:A、B
① 线程A执行,首先锁了金锁,lock成功;然后正准备去锁银锁。。。
② 此时发生了上下文切换
③ 线程B执行,首先锁了银锁,louck成功;然后正准备去锁金锁
④ 此时,死锁发生
线程A因为锁不了银锁,线程无法执行下去,金锁就永远解不开;
线程B因为锁不了金锁,线程无法执行下去,银锁就永远解不开
#include "stdafx.h"
#include<iostream>
#include <thread>
#include<vector>
#include<list>
#include<mutex>
using namespace std;
class A
{
public:
//收集数据线程,相当于写
void inMsgResvQueue()
{
for (int i = 0; i < 1000; i++)//设置1000防止程序一下就执行完
{
cout << "inMsgResvQueue()执行,插入一个元素" << endl;
m_mutex1.lock();//先锁1,再锁2
m_mutex2.lock();
msgRecvQueue.push_back(i);
m_mutex1.unlock();//先解锁那个无所谓
m_mutex2.unlock();
}
}
bool outMsg(int &command)
{
m_mutex2.lock();//先锁2,再锁1
m_mutex1.lock();
if (!msgRecvQueue.empty())//这个也是对消息队列的操作,也要放在锁里面
{
int command = msgRecvQueue.front();//返回第一个元素
msgRecvQueue.pop_front();//删除第一个元素
m_mutex1.unlock();//成对使用
m_mutex2.unlock();//成对使用
return true;
}
m_mutex1.unlock();//成对使用
m_mutex2.unlock();//成对使用
return false;
}
//取出数据线程,相当于读
void outMsgResvQueue()
{
int command = 0;
for (int i = 0; i < 1000; i++)//设置1000防止程序一下就执行完
{
bool result = outMsg(command);
if (result=true)
{
cout << "outMsgResvQueue()执行,取出一个元素" << i << endl;
}
else
{
cout << "outMsgResvQueue()执行,但消息队列为空" << i<<endl;
}
}
}
private:
list<int> msgRecvQueue;//容器,收集命令
mutex m_mutex1;
mutex m_mutex2;
};
int _tmain(int argc, _TCHAR* argv[])
{
A a;
thread myoutMsgThread(&A::outMsgResvQueue,&a);//第二个参数是引用,才能保证线程里用的是同一个对象
thread myinMsgThread(&A::inMsgResvQueue, &a);
myoutMsgThread.join();
myinMsgThread.join();
//创建两个线程,这两个线程阻塞主线程,分别执行
//在线程内,利用互斥量mutex实现共享数据保护
cout << "我的进程开始执行" << endl;
return 0;
}
某一时刻程序卡住了,发生了死锁
死锁解决方案
上锁顺序相同,都是先锁金锁,再锁银锁
4. 条件变量
假设如下场景:
线程A:等待一个条件满足
线程B:专门往消息队列中仍消息
当线程B满足这个条件时,通知线程A
以下介绍:
① 条件变量类condition_variable
② wait()
③ notify_one()/notify_all()
条件变量是一个类,是一个和条件相关的类;
条件变量类需要和互斥量配合工作,用的时候需要生成这个类的对象。
#include "stdafx.h"
#include<mutex>
#include<iostream>
#include <thread>
#include<vector>
#include<list>
#include<condition_variable>
using namespace std;
class A
{
public:
//收集数据线程,相当于写
void inMsgResvQueue()
{
for (int i = 0; i < 1000; i++)//设置1000防止程序一下就执行完
{
cout << "inMsgResvQueue()执行,插入一个元素" << endl;
unique_lock<mutex> sbguard(m_mutex);
msgRecvQueue.push_back(i);
//如果此时outMsgResvQueue()线程正在处理自身的内容,没卡在wait处,则notify_one失效
m_cond.notify_one();//唤醒wait()线程,解锁
}
}
//取出数据线程,相当于读
void outMsgResvQueue()
{
int command = 0;
while (true)
{
unique_lock<mutex> sbguard(m_mutex);
//wait()用来等一个东西
//如果第二个参数lambda表达式返回值是true,那么wait()直接返回,线程继续执行
//如果第二个参数lambda表达式返回值是false,那么wait()将解锁互斥量,并阻塞到本行
//解锁互斥量,则其他线程可以上锁了
//阻塞到另一个线程调用notify_one()函数为止
//如果没有第二个参数,其效果等同于第二个参数返回false;
//当其他线程用notify_one()唤醒wait时,wait会
//①不断尝试获取互斥量,如果获取不到,继续卡在wait这里一直获取;如果获取到,上锁
//②判断wait的第二个参数,如果还为false,解锁互斥量,继续阻塞,等待再次被唤醒
//如果是true,线程继续
//如果是wait没有第二个参数,相当于true,线程继续
m_cond.wait(sbguard, [this]{//第二个参数可以是任何可调用对象
if (!msgRecvQueue.empty())
return true;
else
return false;
});
//只要线程走到这里,表示互斥量一定是锁着的,队列一定有数据
command = msgRecvQueue.front();
msgRecvQueue.pop_front();
sbguard.unlock();//unique_lock灵活,可以随时unlock()解锁
cout << "outMsgResvQueue()执行,取出一个元素" << command<<endl;
//进行别的操作。。。。
}
}
private:
list<int> msgRecvQueue;//容器,收集命令
mutex m_mutex;
condition_variable m_cond;//条件对象
};
int _tmain(int argc, _TCHAR* argv[])
{
A a;
thread myoutMsgThread(&A::outMsgResvQueue,&a);//第二个参数是引用,才能保证线程里用的是同一个对象
thread myinMsgThread(&A::inMsgResvQueue, &a);
myoutMsgThread.join();
myinMsgThread.join();
//创建两个线程,这两个线程阻塞主线程,分别执行
//在线程内,利用互斥量mutex实现共享数据保护
cout << "我的进程开始执行" << endl;
return 0;
}
notify_one只能通知一个线程的wait状态,假设实际中如下场景:
有一个线程在对共享数据进行写,有两个线程在对共享数据进行读;当写进程进行时,wait阻塞两个读进程。但是当写进程结束时,两个读进行应该都可以继续进行,此时用notify_one不合适,需要用notify_all.
5. std::async、std::future创建后台任务
假如希望线程返回一个结果时:
std::async是一个函数模板,用于启动一个异步任务,并返回一个std::future的对象;
std::future是一个类模板。
什么叫启动一个异步任务?就是创建一个线程并开始执行线程。
std::future提供了一种访问异步操作结果的机制,就是这个结果你可能现在拿不到,但是在将来的某个时刻,你就能拿到。
#include "stdafx.h"
#include<mutex>
#include<iostream>
#include <thread>
#include<vector>
#include<list>
#include<future>
using namespace std;
int mythread()
{
cout << "我的线程开始了" << "thread ID=" << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(5000);//休息5s,表示实际中进行线程中的操作消耗了5s
std::this_thread::sleep_for(dura);
cout << "我的线程结束了" << "thread ID=" << std::this_thread::get_id() << endl;
return 5;
}
int _tmain(int argc, _TCHAR* argv[])
{
cout << "我的进程开始执行" << endl;
//std::future<int> result = std::async(mythread);//创建一个线程并开始执行
std::future<int> result = std::async(std::launch::deferred, mythread);//创建一个线程并延迟执行
//如果在可调用对象前加std::launch::deferred表示延迟执行
//延迟执行,等到调用get或者wait时执行
//没有参数表示默认使用std::launch::async
cout << "continue..." << endl;
int def = 0;
cout << result.get() << endl;//打印获得的结果
//result是future对象,它会卡在这里,直到线程返回结果才能继续执行
//get只能调用一次
//result.wait();future的wait函数只会卡在这里等待子线程返回
return 0;
}
补充说明:
std::launch::deferred延迟调用实际上并没有创建子线程,而是在主线程中调用;可以通过线程ID证明,进程(主线程)ID和子线程ID相同
6. std::package_task包装任务
std::package_task是一个类模板,模板参数是各种可调用对象,通过std::package_task把各种可调用对象包装起来,方便将来作为线程入口函数
#include "stdafx.h"
#include<mutex>
#include<iostream>
#include <thread>
#include<vector>
#include<list>
#include<future>
using namespace std;
int mythread(int command)//返回值和参数都是int
{
cout << "我的线程开始了" << "thread ID=" << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(5000);//休息5s,表示实际中进行线程中的操作消耗了5s
std::this_thread::sleep_for(dura);
cout << "我的线程结束了" << "thread ID=" << std::this_thread::get_id() << endl;
return 5;
}
int _tmain(int argc, _TCHAR* argv[])
{
cout << "我的进程开始执行" << "thread ID=" << std::this_thread::get_id() << endl;
std::packaged_task<int(int)> m_pt(mythread);//mythread这个线程入口函数的返回值和参数都是int
//把函数mythread通过packaged_task包装起来
std::thread m_th(std::ref(m_pt),1);//创建线程
//std::ref 用于包装按引用传递的值。
m_th.join();
std::future<int> result = m_pt.get_future();//将packaged_task和future绑定在一起
//future对象里包含线程入口函数的返回结果
cout << result.get() << endl;
return 0;
}
使用容器进行:
vector<std::package_task<int(int)>> mytasks;//容器
std::package_task<int(int)> mypt([](int command){
cout<<command<<endl;
cout << "我的线程开始了" << "thread ID=" << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(5000);
std::this_thread::sleep_for(dura);
cout << "我的线程结束了" << "thread ID=" << std::this_thread::get_id() << endl;
});//创造一个lambda表达式
//存入容器
mytasks.push_back(std::move(mypt));
//此处使用std::move,这样mypt就会空了;而不是直接push_back(mypt),mypt还存在
//取出
std::package_task<int(int)> mypt2;
auto iter=mytasks.begin();
mypt2=std::move(*iter);//使用移动语义
mytasks.erase(iter);//删除第一个元素,iter失效
7. std::promise
std::promise也是一个类模板,我们能够在某个线程中给它赋值,然后我们可以在其他线程中,把这个值取出来用。
#include "stdafx.h"
#include<mutex>
#include<iostream>
#include <thread>
#include<vector>
#include<list>
#include<future>
using namespace std;
void mythreadjob(std::promise<int> &temp, int calc)//返回值和参数都是int
{
//假设做一系列复杂的操作
calc++;
calc += 10;
//假设做其他运算,花费了5s
std::chrono::milliseconds dura(5000);//休息5s,表示实际中进行线程中的操作消耗了5s
std::this_thread::sleep_for(dura);
//结果一系列计算得到结果
int result = calc;
temp.set_value(result);//结果保存到temp对象中
return;
}
void mythreadjob2(std::future<int> &temp)
{
auto result = temp.get();
cout << "mythread2 result=" << result << endl;
return;
}
int _tmain(int argc, _TCHAR* argv[])
{
std::promise<int> m_pro;//声明一个std::promise对象,保存的值类型为int
std::thread mth(mythreadjob,std::ref(m_pro),180);//创建线程
mth.join();//阻塞
//获取结果值
std::future<int> ful = m_pro.get_future();//promise和future
std::thread mth2(mythreadjob2,std::ref(ful));
mth2.join();
return 0;
}
总结:
通过promise保存一个值,在将来某个时刻通过一个future绑定到这个promsie上,进而得到这个绑定的值
上述程序实现了两个线程之间的数据传递,第一个线程计算得到的结果,通过future这个对象传递到了第二个线程
但是,需要注意的是学习这些东西的目的,不是要都用在实际的项目中;相反,如果我们能够用最少的东西写出一个稳定高效的多线程程序,更值得赞赏。学习这些东西的目的,是为了可能读大师高手的代码,进行学习
8. windows临界区
可以认为windows临界区功能等于mutex互斥量
#include "stdafx.h"
#include<iostream>
#include <thread>
#include<vector>
#include<list>
#include<windows.H>
using namespace std;
#define _WINDOWSJQ_;//相当于定义了一个开关
class A
{
public:
A()//构造函数中初始化临界区
{
#ifdef _WINDOWSJQ_
InitializeCriticalSection(&cs);//临界区用之前必须初始化
#endif
}
//收集数据线程,相当于写
void inMsgResvQueue()
{
for (int i = 0; i < 10000; i++)//设置10000防止程序一下就执行完
{
cout << "inMsgResvQueue()执行,插入一个元素" << endl;
#ifdef _WINDOWSJQ_
EnterCriticalSection(&cs);//相当于mutex加锁
msgRecvQueue.push_back(i);
LeaveCriticalSection(&cs);//相当于mutex解锁
#endif
}
}
//取出数据线程,相当于读
void outMsgResvQueue()
{
#ifdef _WINDOWSJQ_
EnterCriticalSection(&cs);//进入临界区,相当于mutex加锁
for (int i = 0; i < 10000; i++)//设置10000防止程序一下就执行完
{
if (!msgRecvQueue.empty())
{
int command = msgRecvQueue.front();//返回第一个元素
msgRecvQueue.pop_front();//删除第一个元素
//进行数据处理
}
}
LeaveCriticalSection(&cs);//离开临界区,相当于解锁
#endif
}
private:
list<int> msgRecvQueue;//容器,收集命令
#ifdef _WINDOWSJQ_
CRITICAL_SECTION cs;//windows中的临界区,相当于C++11的互斥量,用之前必须初始化
#endif
};
int _tmain(int argc, _TCHAR* argv[])
{
A a;
thread myoutMsgThread(&A::outMsgResvQueue, &a);//第二个参数是引用,才能保证线程里用的是同一个对象
thread myinMsgThread(&A::inMsgResvQueue, &a);
myoutMsgThread.join();
myinMsgThread.join();
cout << "我的进程开始执行" << endl;
return 0;
}
9. 线程池浅谈
什么是线程池
假设一下场景
开发一个服务器程序,每来一个客户端,就创建一个新线程为客户提供服务;
① 如果是网友游戏服务器,有200000个玩家同时在线,不可能给每个玩家都创建一个新线程;
② 代码稳定性问题:编写的代码中,偶尔创建一个新线程这种代码,让人感觉到不安
③ 创建线程回收线程会影响程序执行效率
由此引入线程池:
把一堆线程放到一起,统一管理,统一调度。
用的时候,从池子里抓一个过来用,用完的时候,扔会池子里,以供下次使用。
所以,线程池是循环利用线程的方式
线程池实现方式
在程序启动时,一次性创建好一定数量的线程。
在实际程序执行时,不会出现临时创建线程的情况出现,更让人放心。