疫情当前,呆着家里,总结关于工作中遇到的问题和解决思路,是一个很好的学习机会,珍爱生命,懂得感恩,一切会好起来的。
一、写在之前
之前的一篇文章中https://blog.csdn.net/THUNDERDREAMER_OR/article/details/104213670,我提到了网格规划算法的计算量比较大,对三个切面分别进行计算的时候,使用多线程是一个不错的想法,实际上我也是这么做的。现在我们来分析一下,怎样使用C#的Threading自己动手实现多线程并行计算。
知识预热。涉及到多线程,不得不说多因线程之间的通信和竞争而导产生问题,因此锁机制和“死锁”的概念便应用而生。
在竞争态条件下,多个线程对同一竞态资源的抢夺会引发线程安全问题。竞态资源是对多个线程可见的共享资源,主要包括全局(非const)变量、静态(局部)变量、堆变量、资源文件等。
线程之间的竞争,可能带来一系列问题:
为了解决由多进程多资源的同时操作引起的问题,提出了锁机制。在某一时刻只允许有一个进程运行它的临界区代码,从而保证临界资源的中数据的一致性。临界资源是指能够被多个线程共享的数据、资源。临界区代码是指多临界资源操作的那段代码。
这里只介绍互斥锁(mutex lock)的概念和原理,另外一种是自旋锁。这里的互斥,指的就是不同线程之间的互斥性、排他性,即当一个线程在使用临界资源的时候,不允许其他线程对该资源操作。
互斥锁是一种很常见应用也很广的锁,属于sleep-waiting类型,即在锁处于占用状态时,其他线程自动挂起等待,直到该锁释放,线程再次竞争。锁的挂起和释放的切换会消耗一定的性能。
本质上,互斥锁是一个变量(mutex),在使用它时,实际上是对mutex的置0置1操作,mutex状态的改变使线程能获得锁和释放锁。
2. 死锁
维基百科关于死锁(Deadlock)的解释:In concurrent computing, a deadlock is a state in which each member of a group is waiting for another member, including itself, to take action, such as sending a message or more commonly releasing a lock. Deadlock is a common problem in multiprocessing systems, parallel computing, and distributed systems, where software and hardware locks are used to arbitrate shared resources and implement process synchronization.通俗来讲,假设线程A持有锁a,线程B持有锁b,而线程访问临界区的条件是,同时具有锁a和锁b,那么线程A等待线程B释放锁b,线程B等待线程A释放锁a,如果没有外部的作用,线程A、B会一直等待下去,从而产生死锁。
二、代码实现
分析:C#专门提供有线程的类,在System.Threading的命名空间下。在本例中,我们的线程父类需要具备以下的特点:初始化线程并启动线程的方法(Start),标识线程作业是否执行完成的字段(isDone)。
///
/// Template class to make a job using a thread.
///
public class ThreadedJob
{
private bool m_IsDone = false;
private object m_Handle = new object();
private System.Threading.Thread m_Thread = null;
private void Run()
{
ThreadFunction();
IsDone = true;
}
///
/// Is set to true when the job is finished
///
public bool IsDone
{
get
{
bool tmp;
lock (m_Handle)
{
tmp = m_IsDone;
}
return tmp;
}
set
{
lock (m_Handle)
{
m_IsDone = value;
}
}
}
public virtual void Start()
{
m_Thread = new System.Threading.Thread(Run);
m_Thread.Start();
}
public virtual void Abort()
{
if(m_Thread != null)
m_Thread.Abort();
}
public virtual void Interrupt(){
if (m_Thread != null)
m_Thread.Interrupt ();
}
public virtual bool Update()
{
if (IsDone)
{
OnFinished();
return true;
}
return false;
}
protected virtual void ThreadFunction() { }
protected virtual void OnFinished() { }
}
C#中 lock 语句就是互斥锁的实现,如果主线程(或其他子线程同时)访问/修改 IsDone 的变量(临界资源),lock 获取 m_Thread 的互斥锁,如果 m_Thread 处于锁住状态,挂起等待,否则,m_Thread 处于释放状态,锁住该对象,并执行 lock 块中的语句,然后释放 m_Thread。释放 m_Thread 之后,变量 IsDone 被访问/修改,如果有修改,修改结果会同步到其他线程,这样,其他线程在访问 IsDone 变量的时候,是更新过的数据。
using UnityEngine;
public class ThreadJobExecutor : ThreadedJob
{
public ThreadJobExecutor (int threadId)
{
this.ThreadId = threadId;
}
public int ThreadId;
///
/// 核心逻辑区
///
protected override void ThreadFunction()
{
try
{
// here is your heavy job to execute, for example
for (int i = 0; i < 10; i++)
{
Debug.Log("in thread " + ThreadId + " debug info!");
}
}
catch (System.Exception e)
{
Debug.Log("the thread interrupted exeption: " + e.Message);
// clear logic
}
finally
{
}
}
}
下面测试代码:
using System.Collections;
using UnityEngine;
public class Game : MonoBehaviour
{
ThreadJobExecutor[] exectors = new ThreadJobExecutor[3];
private IEnumerator Start()
{
for (int i = 0; i < exectors.Length; i++)
{
exectors[i] = new ThreadJobExecutor(i + 1);
}
for (int i = 0; i < exectors.Length; i++)
{
exectors[i].Start();
}
do
{
Debug.Log("finished job count is: " + finishCount());
yield return null;
}
while (finishCount()!= exectors.Length);
Debug.Log("all jogs hava finished");
}
int finishCount()
{
int cnt = 0;
for (int i = 0; i < exectors.Length; i++)
{
if (exectors[i].IsDone)
cnt++;
}
return cnt;
}
}
测试打印0-9十个数字,不需要等待即可完成,如果是0-99999的打印次数,那么效果很明显。
在一帧之内打印三十次,一次性完成。
打印10000*3次,耗时 (191 +5)帧* Time.deltaTime的时间。
三个线程输出是没有顺序的。
在学习的路上,不会一帆风顺,若有任何错误,欢迎指正。感谢。希望能解决您的问题。