
本文介绍了C#中多线程独占控制并行处理的方法总结,虽然它并不特别依赖于Unity,但已经检查了Unity的操作情况。
独占控制的必要性
在进行多线程并行处理时,需要注意共享变量的处理,例如创建100个任务,将一个变量递增100次。
如下所示:
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;
public class Example : MonoBehaviour
{
public async void Start()
{
var result = 0;
// 创建将结果递增 100 倍的任务的方法
Task CreateTask()
{
return Task.Run(() =>
{
for (var m = 0; m < 100; m++) {
result++; // 不宜同时加工的部分(关键部分)
}
});
}
// 创建上面100个任务并等待完成
var tasks = new List<Task>();
for (var i = 0; i < 100; i++)
{
tasks.Add(CreateTask());
}
await Task.WhenAll(tasks);
// 输出结果为 7555、9558、8613、9015,每次执行都会有所不同(预期结果为 10000)
Debug.Log(result);
}
}
此时预期结果为100*100=10000,但实际结果随着7555、9558、8613、9015每次执行而变化,这是因为当一个线程正在执行计算时,其他线程的计算也在执行。
像这个增量部分这样不应该由多个线程同时处理的部分称为临界区,下面总结了临界区独占控制的方法。
基本独占处理:lockObject
第一个是lockObject的独占控制,所要做的就是创建一个对象类型实例并用lock语句将临界区括起来。
如下面的注释所示:
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;
public class Example : MonoBehaviour
{
public async void Start()
{
var result = 0;
// 实例化对象
var lockObject = new object();
Task CreateTask()
{
return Task.Run(() =>
{
for (var m = 0; m < 100; m++) {
// 用 lock(lockObject){} 将临界区括起来
lock (lockObject)
{
result++;
}
}
});
}
var tasks = new List<Task>();
for (var i = 0; i < 100; i++)
{
tasks.Add(CreateTask());
}
await Task.WhenAll(tasks);
Debug.Log(result);
}
}
成功运行此命令会产生10000个结果。
简单的独家处理:interlock
Interlocked也可以用于简单的运算,如递增、递减、加法等。通过使用定义为Interlocked的类,可以很容易地执行独占控制。
如下所示:
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
public class Example : MonoBehaviour
{
public async void Start()
{
var result = 0;
Task CreateTask()
{
return Task.Run(() =>
{
for (var m = 0; m < 100; m++) {
// 当使用 Interlocked 方法进行处理时,该部分受到独占控制
Interlocked.Increment(ref result);
}
});
}
var tasks = new List<Task>();
for (var i = 0; i < 100; i++)
{
tasks.Add(CreateTask());
}
await Task.WhenAll(tasks);
Debug.Log(result);
}
}
设置并行数上限:SemaphoreSlim
信号量是可以并行执行的数字,例如,通过如下使用SemaphoreSlim类,可以将可并行化的最大任务数设置为3。
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
public class Example : MonoBehaviour
{
[SerializeField] private int _threadCount;
[SerializeField] private int _completeThreadCount;
public async void Start()
{
// 创建一个上限为 3 的信号量
var semaphore = new SemaphoreSlim(3);
Task CreateTask()
{
return Task.Run(() =>
{
try
{
// 如果信号量数量达到上限则等待
semaphore.Wait();
Interlocked.Increment(ref _threadCount);
Thread.Sleep(300);
Interlocked.Decrement(ref _threadCount);
Interlocked.Increment(ref _completeThreadCount);
}
finally
{
// 释放信号量
semaphore.Release();
}
});
}
var tasks = new List<Task>();
for (var i = 0; i < 100; i++)
{
tasks.Add(CreateTask());
}
await Task.WhenAll(tasks);
Debug.Log("Complete");
}
}
如果播放这个并查看检查器,可以看到任务始终以上限3运行。

Semaphore和SemaphoreSlim
除了SemaphreSlim之外,还有一个Semaphore类,它的使用方式与SemaphoreSlim相同。
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
public class Example : MonoBehaviour
{
[SerializeField] private int _threadCount;
[SerializeField] private int _completeThreadCount;
public async void Start()
{
// 创建一个上限为 3 的信号量
var semaphore = new Semaphore(3, 3);
Task CreateTask()
{
return Task.Run(() =>
{
try
{
// 如果信号量数量达到上限则等待
semaphore.WaitOne();
Interlocked.Increment(ref _threadCount);
Thread.Sleep(300);
Interlocked.Decrement(ref _threadCount);
Interlocked.Increment(ref _completeThreadCount);
}
finally
{
// 释放信号量
semaphore.Release();
}
});
}
var tasks = new List<Task>();
for (var i = 0; i < 100; i++)
{
tasks.Add(CreateTask());
}
await Task.WhenAll(tasks);
Debug.Log("Complete");
}
}
然而,这似乎在多进程应用程序中使用,因为它可以被其他进程命名和引用,可以从这里了解Semaphore and SemaphoreSlim。
事件等待句柄
没有必要像这次那样使用它来进行独占控制,但是也有一种方法可以使用EventWaitHandle。
…
以上是关于Unity多线程独占控制并行处理的方法的全部内容,如果你有任何反馈,请随时在本页面下方留言。