C++并发与多线程

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个玩家同时在线,不可能给每个玩家都创建一个新线程;
② 代码稳定性问题:编写的代码中,偶尔创建一个新线程这种代码,让人感觉到不安
③ 创建线程回收线程会影响程序执行效率

由此引入线程池:
把一堆线程放到一起,统一管理,统一调度。
用的时候,从池子里抓一个过来用,用完的时候,扔会池子里,以供下次使用。
所以,线程池是循环利用线程的方式

线程池实现方式
在程序启动时,一次性创建好一定数量的线程。
在实际程序执行时,不会出现临时创建线程的情况出现,更让人放心。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值