3 —— 多线程的同步和异步

多线程的同步和异步


一 同步和异步概念

  异步是当一个调用或请求发送被调用者,调用者不用等待其结果的返回而继续当前的处理。实现异步机制的方式有多线程、中断和消息等。
  线程同步就是让多个线程正确且有序的共享数据,以一致的顺序执行一组操作。


示例:创建多个线程

#include <iostream>
#include <thread>
#include <vector>

using namespace std;


//	 线程入口函数
void myprint(int num)
{
	cout << this_thread::get_id() << "thread start , num is " << num << endl;
	// do others ... 
	cout << this_thread::get_id() << "thread end " << endl;

	return;
}


int main()
{
	// 创建并等待多个线程
	vector <thread> mythreads;

	for (size_t i = 0; i < 10; ++i)
	{
		mythreads.push_back(thread(myprint,i)); //	创建10个线程,同时,这10个线程已经开始执行
	}

	for (auto iter = mythreads.begin(); iter !=  mythreads.end(); ++iter)
	{
		iter->join();	// 等待10个线程都返回
	}

	cout << "I love China! " << endl;
	return 0;
}
1 多个线程,每个线程执行顺序是乱的。先创建的线程不一定比后创建的线程执行快。这与操作系统中线程调度机制有关。
2 把thread对象放到一个容器中,方便对大量线程进行管理。

二 多个线程建立安全数据共享

如果是只读数据,多线程同时进行访问不需要特别的处理,直接读取,安全稳定。
如果是读写数据,读与写时应该分别加锁,为防止读与写同时进行。

示例:模仿消息队列读写命令

#include <iostream>
#include <thread>
#include <list>
#include <mutex>


using namespace std;


class A
{
public:
	// 把收到的消息传入队列
	void inMsgRecvQueue()
	{
		for (size_t i = 0; i < 1000; ++i)
		{
			cout << "收到消息,并放入队列 " << i << endl;

			my_mutex.lock();
			msgRecvQueue.push_back(i);
			my_mutex.unlock();
		}

		cout << "消息入队结束" << endl;
	}

	// 从队列中取出消息
	void outMsgRecvQueue()
	{
		for (size_t i = 0; i < 1000; ++i)
		{
			my_mutex.lock();
			if (!msgRecvQueue.empty())
			{
				// 队列不为空
				int num = msgRecvQueue.front();
				cout << "从消息队列中取出 " << num << endl;
				msgRecvQueue.pop_front();
				my_mutex.unlock();
			}
			else
			{
				// 消息队列为空
				cout << "消息队列为空 " << endl;
				my_mutex.unlock();
			}
		}

		cout << "消息出队结束" << endl;
	}


private:
	list<int> msgRecvQueue;	//	容器,存放消息。 list容器频繁的插入和删除数据时效率较高,vector容器随机的插入与删除时效率较高。
	mutex my_mutex;	//	创建一个互斥量
};


int main()
{
	A myobj;
	thread	myInMsgObj(&A::inMsgRecvQueue, &myobj); //	第二个参数是引用,才能保障线程中用的是同一对象
	thread	myOutMsgObj(&A::outMsgRecvQueue, &myobj);
	myInMsgObj.join();
	myOutMsgObj.join();

	return 0;
}

三 互斥量

  在了解互斥锁之前,需要了解一下临界资源与临界区的概念:
  所谓临界资源,是一次仅一个线程使用的共享资源。对于临界资源,各线程应该互斥地对其访问。每个线程中访问临界资源的那段代码称为临界区。任何时候,处于临界区内的线程不可多于一个。若已有线程进入自己的临界区,则其他所有试图进入临界区的进程必须等待。进入临界区的线程要在有限时间内退出,以便其他线程能及时进入自己的临界区。如果进程不能进入自己的临界区,则应该让出CPU(阻塞),避免出现进程“忙等”的情况。

  互斥量是一个类对象,理解成一把锁。多个线程尝试用lock()函数对这把锁加锁,只有一个线程能加锁成功。如果没有加锁成功,那么当前线程卡在lock()这,并不断尝试去加锁。它的功能就是,同一时刻,只允许一个线程对临界区进行访问。

3.1 互斥量用法

步骤:先lock(),再操作共享数据,unlock(),最后不用互斥锁的时候再销毁它。
note:
 1 lock()与unlock()成对使用,大多数人会忘记unlock()
 2 互斥量锁住的内容不能多,也不能少。 多则影响程序效率,少则不安全

3.2 std::lock_guard

  std::lock_guard是一个类模板,它可以传入mutex对象。在构造函数中调用mutex.lock(),析构函数中调用mutex.unlock()。它可以用来防止忘记对mutex.unlock()的问题。通过 {} 作用域运算符来控制std::lock_guard的生命周期,从而控制加锁解锁。

示例:

void inMsgRecvQueue()
{
	for (size_t i = 0; i < 1000; ++i)
	{
		cout << "收到消息,并放入队列 " << i << endl;

		std::lock_guard<mutex> my_guard(my_mutex);
		msgRecvQueue.push_back(i);
	}

	cout << "消息入队结束" << endl;
}

// 从队列中取出消息
void outMsgRecvQueue()
{
	for (size_t i = 0; i < 1000; ++i)
	{
		std::lock_guard<mutex> my_guard(my_mutex);
		if (!msgRecvQueue.empty())
		{
			// 队列不为空
			int num = msgRecvQueue.front();
			cout << "从消息队列中取出 " << num << endl;
			msgRecvQueue.pop_front();
		}
		else
		{
			// 消息队列为空
			cout << "消息队列为空 " << endl;
		}
	}

	cout << "消息出队结束" << endl;
}

四 死锁

  线程相互等待,形成死锁。也就是说,多个互斥量,在不同线程中,加锁的顺序不一致,导致进程卡死。如果在同一线程中对mutex加锁两次(可以用std::recursive_mutex代替),那也会造成死锁。

示例:

#include <iostream>
#include <thread>
#include <list>
#include <mutex>


using namespace std;


class A
{
public:
	// 把收到的消息传入队列
	void inMsgRecvQueue()
	{
		for (size_t i = 0; i < 1000; ++i)
		{
			cout << "收到消息,并放入队列 " << i << endl;

			my_mutex1.lock();
			my_mutex2.lock();
			msgRecvQueue.push_back(i);
			my_mutex1.unlock();
			my_mutex2.unlock();
		}

		cout << "消息入队结束" << endl;
	}

	// 从队列中取出消息
	void outMsgRecvQueue()
	{
		for (size_t i = 0; i < 1000; ++i)
		{
			my_mutex2.lock();	// 与入队列的线程加锁顺序相反
			my_mutex1.lock();
			if (!msgRecvQueue.empty())
			{
				// 队列不为空
				int num = msgRecvQueue.front();
				cout << "从消息队列中取出 " << num << endl;
				msgRecvQueue.pop_front();
				my_mutex1.unlock();	//	解锁顺序没有要求
				my_mutex2.unlock();
			}
			else
			{
				// 消息队列为空
				cout << "消息队列为空 " << endl;
				my_mutex1.unlock();
				my_mutex2.unlock();
			}
		}

		cout << "消息出队结束" << endl;
	}


private:
	list<int> msgRecvQueue;	//	容器,存放消息。 list容器频繁的插入和删除数据时效率较高,vector容器随机的插入与删除时效率较高。
	mutex my_mutex1,my_mutex2;	//	死锁至少需要两个互斥量

};


int main()
{
	A myobj;
	thread	myInMsgObj(&A::inMsgRecvQueue, &myobj); //	第二个参数是引用,才能保障线程中用的是同一对象
	thread	myOutMsgObj(&A::outMsgRecvQueue, &myobj);
	myInMsgObj.join();
	myOutMsgObj.join();

	return 0;
}

4.1 死锁解决方法

  在多个线程中,确保对每个互斥量加锁的顺序是一致的,就可以避免死锁。
  std::lock()函数模板,作用是一次锁住两个或者以上的互斥量。在加锁时,先加锁第一个互斥量,再加锁第二个,第三个… 如果加锁其中一个失败,则所有的互斥量都不加锁。但是,解锁过程必须自己添加。

示例:

	// 把收到的消息传入队列
	void inMsgRecvQueue()
	{
		for (size_t i = 0; i < 1000; ++i)
		{
			cout << "收到消息,并放入队列 " << i << endl;

			std::lock(my_mutex1, my_mutex2);	//	 同时加锁两个互斥量,若加锁其中一个失败,则都不加锁

			msgRecvQueue.push_back(i);
			my_mutex1.unlock();	//	解锁不能少
			my_mutex2.unlock();
		}

		cout << "消息入队结束" << endl;
	}

	// 从队列中取出消息
	void outMsgRecvQueue()
	{
		for (size_t i = 0; i < 1000; ++i)
		{
			std::lock(my_mutex1, my_mutex2);	//	 同时加锁两个互斥量,若加锁其中一个失败,则都不加锁
			if (!msgRecvQueue.empty())
			{
				// 队列不为空
				int num = msgRecvQueue.front();
				cout << "从消息队列中取出 " << num << endl;
				msgRecvQueue.pop_front();
				my_mutex1.unlock();	
				my_mutex2.unlock();
			}
			else
			{
				// 消息队列为空
				cout << "消息队列为空 " << endl;
				my_mutex1.unlock();
				my_mutex2.unlock();
			}
		}

		cout << "消息出队结束" << endl;
	}

4.2 std::adopt_lock

  如果解锁过程也想让它管理,可以使用std::adopt_lock;

  std::adopt_lock 是个结构体对象,相当于一个标志。作用:告诉std::lock_guard构造时,不要对mutex进行lock(),但是析构时,还是会执行unlock();

// 把收到的消息传入队列
void inMsgRecvQueue()
{
	for (size_t i = 0; i < 1000; ++i)
	{
		cout << "收到消息,并放入队列 " << i << endl;

		std::lock(my_mutex1, my_mutex2);
		lock_guard<mutex> my_guard1(my_mutex1, std::adopt_lock);	//	将my_mutex1,my_mutex2托管给lock_guard,自动解锁
		lock_guard<mutex> my_guard2(my_mutex2, std::adopt_lock);

		msgRecvQueue.push_back(i);
	}

	cout << "消息入队结束" << endl;
}

// 从队列中取出消息
void outMsgRecvQueue()
{
	for (size_t i = 0; i < 1000; ++i)
	{
		std::lock(my_mutex1, my_mutex2);
		lock_guard<mutex> my_guard1(my_mutex1, std::adopt_lock);
		lock_guard<mutex> my_guard2(my_mutex2, std::adopt_lock);

		if (!msgRecvQueue.empty())
		{
			// 队列不为空
			int num = msgRecvQueue.front();
			cout << "从消息队列中取出 " << num << endl;
			msgRecvQueue.pop_front();
		}
		else
		{
			// 消息队列为空
			cout << "消息队列为空 " << endl;
		}
	}

	cout << "消息出队结束" << endl;
}
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值