使用容器的方法创建和管理多线程

把thresd的对象放到容器中,看起来像thread对象数组,使用这种方法对于我们一次创建大量线程并对其进行管理很方便

创建一个简单的多线程:

#include<iostream>
#include<thread>
#include<vector>
using namespace std;

void show(int i)
{
	cout << "子线程开始执行,线程编号为:" << i << "   id编号为:" << std::this_thread::get_id() << "的线程"<<endl;
	//.....
}

int main()
{
	vector<thread> test;

	for (int i = 0; i < 10; i++)
	{
		test.push_back(thread(show, i));
	}
	for (auto j = test.begin(); j != test.end(); ++j)
	{
		j->join();
	}

	cout << "主进程开始执行" << "   id编号为:" << std::this_thread::get_id() << "的线程"<<endl;
	system("pause");
}

由结果可以知道:多个线程执行顺序是乱的,跟操作系统操作系统内部对线程的运行调度机制有关。

上面便是一种简单的创建多线程的方法。

共享数据:

分为两种

  1. 只读数据:是安全稳定的,不需要什么特别的手段,直接读就可以。
  2. 有读有写:2个线程写,8个线程读。如果不特殊处理,程序就会崩溃
    处理方法,读的时候不能写,写的时候不能读。2个线程不能同时写,8个线程不能同时读

eg:火车票购票时,总共有10个购票窗口,1号窗口和2号窗口要购买同一张票,如果程序不加以处理,就会产生很严重的问题。

一,只读数据:

#include<iostream>
#include<thread>
#include<vector>
using namespace std;

vector<int> num = { 1, 2, 3 };
void show(int i)
{
	cout << "子线程开始执行,线程编号为:" << i <<" 数据:"<< num[0] << " " << num[1] << " " << num[2] << " " << endl;
	//.....
}

int main()
{
	vector<thread> test;

	for (int i = 0; i < 10; i++)
	{
		test.push_back(thread(show, i));
	}
	for (auto j = test.begin(); j != test.end(); ++j)
	{
		j->join();
	}

	cout << "主进程开始执行" << "   id编号为:" << std::this_thread::get_id() << "的线程"<<endl;
	system("pause");
}

可以看出,尽管线程不是按照一定的顺序执行,但是数据都能够显示出来。

二,有读有写:

第二种方法可能如果两个线程同时产生读写操作,则会引发系统崩溃的问题,则需要通过互斥量等来解决问题。

    解决方法:保护共享数据,操作时,用代码把共享数据锁住,操作数据,解锁
                      其他想操作共享数据的线程必须等待解锁,锁住,操作,解锁

案例:

网络游戏服务器,两个自己创建的线程,一个线程收集玩家命令(用一个数字表示),并把命令数据写到一个队列中。
                                                                另一个线程,从队列中取出玩家发来的命令,解析,然后执行玩家需要的命令

互斥量

    一,互斥量(mutex)的基本概念

        互斥量是个类对象,理解成一把锁,多个线程尝试用lock()成员函数来加锁这把锁头,只有一个线程能锁定成功(成功的标志是lock()函数返回)
        如果没锁成功,那么流程卡在lock()这里不断尝试去锁这把锁头
        需要注意保护量的大小,不能过多也不能太少

    二,互斥量的用法    

  1. lock(),unlock()
    步骤:先lock(),操作共享数据,unlock();
    lock()和unlock()要成对使用,两者缺一不可,不允许调用不同数量的lock()和unlock(),否则程序可能崩溃
  2. 为了防止忘记unlock(),引入了一个std::lock_guard的类模板
    lock_guard类模板:直接取代lock()和unlock();也就是说,用了lock_guard之后,就不能使用lock()和unlock()
                                 lock_guard构造函数里执行了mutex.lock();
                                 lock_guard析构函数里执行了mutex.unlock();
  3. std:lock()函数模板
    能力:一次锁定多个互斥量:一次锁住两个或两个以上的互斥量(至少两个或两个以上,一个不行)
    它不存在这种因为在多个线程中,因为锁的顺序问题而导致死锁的风险问题
     如果互斥量中有一个没锁住,它就在那里等着,等所有互斥量都锁住,它才能往下走(返回)
             要么两个互斥量都锁住,要么两个都没锁住,如果只锁定了一个,另外一个没锁住,则它立刻把已经锁住的解锁
    因此不用担心多个锁的顺序问题
  4. std::lock_guard的std::adopt_lock参数
    std::adopt_lock是个结构体对象,起一个标记作用;作用就是这个互斥量已经lock(),
                           不需要再std::lock_guard(std::mutex)里面对对象进行再次lock()了。
    主要可以与lock()或者std::lock()函数模板连用

假设现在有线程a和线程b,并且有两个锁x,y;

  • lock(),unlock()使用方法

线程a:x.lock();y.lock();          ...........             x.unlock();y.unlock();
线程b:x.lock();y.lock();  (x,y的顺序需要和线程a相同)          ...........             x.unlock();y.unlock();

  • std::lock_guard的使用用法

线程a:std::lock_guard<mutex> abguard(x);
             std::lock_guard<mutex> abguard(y);        (因为该类模板会自动解锁,所以不用再使用unlock())
线程b:std::lock_guard<mutex> abguard(x);
             std::lock_guard<mutex> abguard(y);

  • std:lock()的使用方法

线程a:std::lock(x,y);               ............           x.unlock();y.unlock();
线程b:std::lock(y,x);   (x,y的顺序可以与线程a的不同)            ............           x.unlock();y.unlock();

  • std::lock_guard的std::adopt_lock参数

线程a:x.lock();y.lock();       std::lock_guard<mutex> abguard(x);
                                             std::lock_guard<mutex> abguard(y);
线程b:std::lock(x,y);       std::lock_guard<mutex> abguard(x,std::adopt_lock);
                                         std::lock_guard<mutex> abguard(y,std::adopt_lock);

死锁问题:

举例:

    比如我有两把锁(死锁这个问题,是由至少两个锁头也就是两个互斥锁才能产生):假设现在有金锁和银锁
    两个线程a和线程b

  1. 线程a执行的时候,这个线程先锁金锁,把金锁lock()成功后,他去lock银锁
    此时出现了上下文切换的情况
  2. 线程b执行,这个线程先锁银锁,因为银锁还没有被锁,所以银锁会被lock()成功,线程B要去lock()金锁
    此时此刻,死锁就产生了
  3. 线程a因为拿不到银锁头,流程走不下(所有后面有解锁金锁头的流程走不下去,所以金锁头解不开)
  4. 线程b因为拿不到金锁头,流程走不下(所有后面有解锁银锁头的流程走不下去,所以银锁头解不开)
  5. 此刻线程a和线程b就都无法执行下去,产生了死锁的情况

解决方法:

当有多个锁时,锁的顺序应该相同

因此在编写多线程的程序时,当出现多个锁时,需要注意锁的执行顺序,避免出现死锁的情乱。

代码:

#include<iostream>
#include<vector>
#include<thread>
#include<list>
#include<mutex>
using namespace std;

class A
{
public:
	//把收到的玩家命令,入到一个队列的线程
	void inMsgRecvQueue()
	{
		for (int i = 0; i < 10000; ++i)
		{
			cout << "inMsgRecvQueue()执行,插入一个元素" << i << endl;
			my_mutex1.lock();
			my_mutex2.lock();
			msgRecvQueue.push_back(i);   //把收到的命令,放到消息队列中
			my_mutex1.unlock();
			my_mutex2.unlock();
		}
	}

	bool outMsgLUL(int &command)
	{
		//std::lock_guard<mutex> abguard(my_mutex);
		//想要提前结束,则可以使用{},将这句话放到{}之内执行
		//my_mutex1.lock();
		//my_mutex2.lock();
		lock(my_mutex2, my_mutex1);

		std::lock_guard<mutex> abguard1(my_mutex1, std::adopt_lock);
		std::lock_guard<mutex> abguard2(my_mutex2, std::adopt_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 outMsgRecvRueue()
	{
		int command = 0;
		for (int i = 0; i < 100000; ++i)
		{
			bool YN = outMsgLUL(command);
			if (YN)
			{
				cout << "outMsgRecvQueue()执行,取出一个元素" << command << endl;
				//可以考虑进行命令(数据)处理
				//....
			}
			else
			{
				//消息队列为空
				cout << "outMsgRecvQueue()执行,但是目前消息队列为空" << i << endl;
			}
		}
	}
private:
	list<int> msgRecvQueue;
	mutex my_mutex1;    //创建一个互斥量
	mutex my_mutex2;
};

int main()
{
	A a;
	thread test1(&A::outMsgRecvRueue, &a);//第二个参数是引用才能保证
	thread test2(&A::inMsgRecvQueue, &a);//第二个参数是引用才能保证
	test1.join();
	test2.join();

	system("pause");
	return 0;
}

 

更多推荐