【多线程】线程的同步

本文介绍了多线程中线程同步和互斥的概念,强调了它们在处理共享资源时的重要性。通过C#的lock、Monitor等方法展示了如何实现线程同步,并提供了一个利用Monitor的Wait()和Pulse()方法实现线程协同执行的示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

多线程共享一个进程的地址空间,虽然线程间通信容易进行,但是多线程同时访问共享对象时需要引入同步和互斥机制。

线程同步:多个任务按照约定的顺序相互配合完成一件事,基于信号量的概念提出了一种同步机制,由信号量来决定线程是继续运行还是阻塞等待。

线程互斥:线程间同步访问临界资源时的互相等待,引入互斥锁的目的是用来保证共享资源数据操作的完整性。互斥锁主要用来保护临界资源,每个临界资源都有一个互斥锁来保护,任何时刻最多只能有一个线程能访问该资源。线程必须先获得互斥锁才能访问临界资源,访问玩临界资源后释放该锁。如果无法获得锁,线程会阻塞直到获得锁为止。临界资源是指每次仅允许一个进程访问的资源,属于临界资源的硬件有打印机、磁带机等,软件有消息缓冲队列、变量、数组、缓冲区等,线程间应采取互斥方式,实现对这种资源的共享。每个线程中访问临界资源的那段代码称为临界区

在C#中实现线程的同步有几种方法:lock、Mutex、Monitor、Semaphore、Interlocked和ReaderWriterLock等。同步策略也可以分为同步上下文、同步代码区、手动同步几种方式。

1、同步代码区
同步代码区,它是针对特定部分代码进行同步的一种方法。

(1)、lock同步, lock语句是一种有效的、不跨越多个方法的小代码块同步的做法,也就是使用lock语句只能在某个方法的部分代码之间,不能跨越方法。

       参考程序:

public class ThreadTwo : MonoBehaviour 
{
	private Thread _trdOne;
	private Thread _trdTwo;

	private List<string> _slTricket = new List<string>();

	private object _oLock = new object();

	void Start () 
	{
		TricketList();

		_trdOne = new Thread(new ThreadStart(Run));	
		_trdTwo = new Thread(new ThreadStart(Run));
		_trdOne.Name = "trdOne";
		_trdTwo.Name = "trdTwo";
		_trdOne.Start();
		_trdTwo.Start();

	}
	void TricketList()
	{
		for(int i = 1 ;i <= 100 ;i++)
		{
			_slTricket.Add(i.ToString().PadLeft(3,'0'));
		}
	}

	void Run()
	{
		lock(_oLock)
		{
			while(_slTricket.Count >0)
			{

				string m_sTricket = _slTricket[0];
				print(Thread.CurrentThread.Name + " 售出一张票,票号: " + m_sTricket);
				_slTricket.RemoveAt(0);
			}
		}
	}
}

(2)、Monitor类

       参考程序:

void Run()
	{
		Monitor.Enter(_oLock);
			while(_slTricket.Count >0)
			{
				
				string m_sTricket = _slTricket[0];
				print(Thread.CurrentThread.Name + " 售出一张票,票号: " + m_sTricket);
				_slTricket.RemoveAt(0);
			}
		Monitor.Exit(_oLock);
	}

这段代码最终运行的效果和使用lock关键字来同步的效果一样。比较之下,大家会发现使用lock关键字来保持同步的差别不大:”lock(objLock){“被换成了”Monitor.Enter(objLock);”,而”}”被换成了” Monitor.Exit(objLock);”。

实际上如果你通过其它方式查看最终生成的IL代码,你会发现使用lock关键字的代码实际上是用Monitor来实现的。

lock (oLock){
    //同步代码
}

实际上是相当于:

try{
	Monitor.Enter(objLock);
	//同步代码
}
finally
{
	Monitor.Exit(objLock);
}

Monitor类除了Enter()和Exit()方法之外,还有Wait()和Pulse()方法。Wait()方法是临时释放当前获得的锁,并使当前对象处于阻塞状态,Pulse()方法是通知处于等待状态的对象可以准备就绪了,它一会就会释放锁。

下面我们利用这两个方法来完成一个协同的线程,一个线程负责随机产生数据,一个线程负责将生成的数据显示出来。参考程序:

using UnityEngine;
using System;
using System.Collections;
using System.Threading;

public class ThreadThree : MonoBehaviour {

	private Thread _trdOne;
	private Thread _trdTwo;
	void Start () 
	{
		ThreadWaitAndPluse m_cWaitAPluse =new ThreadWaitAndPluse();

		Thread _trdOne = new Thread(new ThreadStart(m_cWaitAPluse.ThreadMethodOne));
		_trdOne.Start();
		
		Thread _trdTwo = new Thread(new ThreadStart(m_cWaitAPluse.ThreadMethodTwo));
		_trdTwo.Start();

	}
}

public class ThreadWaitAndPluse
{
	private object _oLock;
	private int _iRandomNum;
	private System.Random _radRandom;

	public ThreadWaitAndPluse()
	{
		_oLock = new object();
		_radRandom = new System.Random();
	}

	//显示随机生成的数据
	public void ThreadMethodOne()
	{
		//获取对象锁
		Monitor.Enter(_oLock);
		Debug.Log("当前进入的线程:显示随机线程" + Thread.CurrentThread.GetHashCode());

		for (int i = 0; i < 5; i++)
		{
			//释放对象锁,并阻止当前线程
			Monitor.Wait(_oLock);

			Debug.Log("显示随机值线程 :工作");
			Debug.Log("显示随机值线程 :得到了数据,RandomNum= " + _iRandomNum + " ;Thread ID= " + Thread.CurrentThread.GetHashCode());

			//通知其它等待锁的对象状态已经发生改变,当这个对象释放锁之后等待锁的对象将会活得锁
			Monitor.Pulse(_oLock);
		}
		Debug.Log("退出当前线程:显示随机线程" + Thread.CurrentThread.GetHashCode());
		//释放对象锁
		Monitor.Exit(_oLock);
	}

	//生成随机数据
	public void ThreadMethodTwo()
	{
		//获取对象锁
		Monitor.Enter(_oLock);
		Debug.Log("当前进入的线程:生成随机线程" + Thread.CurrentThread.GetHashCode());

		for (int i = 0; i < 5; i++)
		{
			//通知其它等待锁的对象状态已经发生改变,当这个对象释放锁之后等待锁的对象将会活得锁
			Monitor.Pulse(_oLock);

			Debug.Log("生成随机值线程 :工作");
			_iRandomNum = _radRandom.Next(DateTime.Now.Millisecond) ;//生成随机数
			Debug.Log("生成随机值线程 :生成了数据,RandomNum=" + _iRandomNum + " ;Thread ID=" + Thread.CurrentThread.GetHashCode());

			//释放对象锁,并阻止当前线程
			Monitor.Wait(_oLock);
		}
		Debug.Log("退出当前线程:生成随机线程" + Thread.CurrentThread.GetHashCode());
		//释放对象锁
		Monitor.Exit(_oLock);
	}
}

运行结果:


一般情况下会看到上面的结果,原因是_trdOne的Start()方法在先,所以一般会优先获得执行,_trdOne执行后首先获得对象锁,然后在循环中通过Monitor.Wait(lockObject)方法临时释放对象锁,_trdOne这时处于阻塞状态;这样_trdTwo获得对象锁并且得以执行,trdTwo进入循环后通过Monitor.Pulse(lockObject)方法通知等待同一个对象锁的_trdOne准备好,然后在生成随机数之后临时释放对象锁;接着_trdOne获得了对象锁,执行输出trdTwo生成的数据,之后_trdOne通过Monitor.Wait(lockObject)通知trdTwo准备就绪,并在下一个循环中通过 Monitor.Wait(lockObject)方法临时释放对象锁,就这样_trdOnetrdTwo交替执行,得到了上面的结果。

但是有时也会出现_trdTwo优先执行的情况出现,尽管_trdOne.Start() _trdTwo.Start() 之前,但是不一定_trdOne 一定在_trdTwo 之前执行。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值