Sta*_* BZ 23 c# multithreading unity-game-engine
我想了解如何编写线程安全代码.
例如,我在我的游戏中有这个代码:
bool _done = false;
Thread _thread;
// main game update loop
Update()
{
// if computation done handle it then start again
if(_done)
{
// .. handle it ...
_done = false;
_thread = new Thread(Work);
_thread.Start();
}
}
void Work()
{
// ... massive computation
_done = true;
}
Run Code Online (Sandbox Code Playgroud)
如果我理解正确,可能会发生主游戏线程和我_thread
可以拥有自己的缓存版本_done
,并且一个线程可能永远不会_done
在另一个线程中看到更改?
如果可能,如何解决?
是否可以解决,只应用volatile
关键字.
或者是有可能读取和写入,通过价值Interlocked
的类似的方法Exchange
和Read
?
如果我包围_done
读写操作lock (_someObject)
,需要我使用Interlocked
什么东西来防止缓存?
编辑1
_done
as volatile
并Update
从多个线程调用方法.if
在分配_done
给false 之前,2个线程是否可能输入语句?Mar*_*ell 15
是的,但从技术上讲,这不是volatile
关键字的作用 ; 然而,它具有副作用的结果 - 并且大多数用途volatile
都是为了副作用; 实际上volatile
现在的MSDN文档只列出了这个副作用场景(链接) - 我想实际的原始措辞(关于重新排序指令)太混乱了?所以也许这是目前官方的使用情况如何?
没有bool
方法Interlocked
; 你需要使用一个int
与像值0
/ 1
,但是这几乎是什么bool
是反正 -请注意,Thread.VolatileRead
也将工作
lock
有一个完整的围栏; 你不需要任何额外的构造,lock
它本身就足以让JIT理解你需要什么
就个人而言,我会使用volatile
.您已经方便地列出了1/2/3增加的开销顺序.volatile
将是这里最便宜的选择.
Fab*_*jan 11
虽然您可以使用volatile
关键字作为bool标志,但它并不总能保证对该字段的线程安全访问.
在你的情况下,我可能会创建一个单独的类,Worker
并使用事件来通知后台任务完成执行:
// Change this class to contain whatever data you need
public class MyEventArgs
{
public string Data { get; set; }
}
public class Worker
{
public event EventHandler<MyEventArgs> WorkComplete = delegate { };
private readonly object _locker = new object();
public void Start()
{
new Thread(DoWork).Start();
}
void DoWork()
{
// add a 'lock' here if this shouldn't be run in parallel
Thread.Sleep(5000); // ... massive computation
WorkComplete(this, null); // pass the result of computations with MyEventArgs
}
}
class MyClass
{
private readonly Worker _worker = new Worker();
public MyClass()
{
_worker.WorkComplete += OnWorkComplete;
}
private void OnWorkComplete(object sender, MyEventArgs eventArgs)
{
// Do something with result here
}
private void Update()
{
_worker.Start();
}
}
Run Code Online (Sandbox Code Playgroud)
您可以根据需要随意更改代码
PS Volatile在性能方面表现良好,在您的场景中,它应该工作,因为它看起来像是以正确的顺序进行读写.可能通过新读/写精确地实现了内存屏障 - 但MSDN规范无法保证.由您决定是否承担使用volatile
或不使用的风险.
小智 6
也许您甚至不需要_done变量,因为如果使用线程的IsAlive()方法,您可以实现相同的行为.(假设你只有1个后台线程)
像这样:
if(_thread == null || !_thread.IsAlive())
{
_thread = new Thread(Work);
_thread.Start();
}
Run Code Online (Sandbox Code Playgroud)
我没有测试这个顺便说一句..这只是一个建议:)
System.Threading.Thread.MemoryBarrier()
是这里的正确工具.代码可能看起来很笨重,但它比其他可行的替代方案更快.
bool _isDone = false;
public bool IsDone
{
get
{
System.Threading.Thread.MemoryBarrier();
var toReturn = this._isDone;
System.Threading.Thread.MemoryBarrier();
return toReturn;
}
private set
{
System.Threading.Thread.MemoryBarrier();
this._isDone = value;
System.Threading.Thread.MemoryBarrier();
}
}
Run Code Online (Sandbox Code Playgroud)
volatile
不会阻止读取旧值,因此它不符合此处的设计目标.有关更多信息,请参阅Jon Skeet的解释或C#中的Threading.
请注意,由于未定义的行为,在许多情况下volatile
可能会起作用,特别是在许多常见系统上的强内存模型.但是,当您在其他系统上运行代码时,依赖未定义的行为可能会导致出现错误.一个实际的例子就是你在Raspberry Pi上运行这个代码(现在可能是因为.NET Core!).
编辑:在讨论了" volatile
在这里不起作用" 的说法后,目前还不清楚C#规范究竟保证了什么; 可以说,volatile
可能会保证有效,尽管它有更大的延迟. MemoryBarrier()
仍然是更好的解决方案,因为它确保更快的提交.在" 为什么我需要内存屏障? "中讨论的"果壳中的C#4"中的示例中解释了这种行为.
锁是一种更重的机制,用于更强的过程控制.在这样的应用程序中,它们不必要地笨拙.
性能影响很小,你可能不会注意到光线使用,但它仍然是次优的.此外,它可以(如果稍微)对线程饥饿和死锁等较大问题做出贡献.
为了演示这个问题,这里是Microsoft的.NET源代码(通过ReferenceSource):
public static class Volatile
{
public static bool Read(ref bool location)
{
var value = location;
Thread.MemoryBarrier();
return value;
}
public static void Write(ref byte location, byte value)
{
Thread.MemoryBarrier();
location = value;
}
}
Run Code Online (Sandbox Code Playgroud)
所以,假设一个线程设置_done = true;
,然后另一个读取_done
以检查它是否是true
.如果我们内联它会是什么样子?
void WhatHappensIfWeUseVolatile()
{
// Thread #1: Volatile write
Thread.MemoryBarrier();
this._done = true; // "location = value;"
// Thread #2: Volatile read
var _done = this._done; // "var value = location;"
Thread.MemoryBarrier();
// Check if Thread #2 got the new value from Thread #1
if (_done == true)
{
// This MIGHT happen, or might not.
//
// There was no MemoryBarrier between Thread #1's set and
// Thread #2's read, so we're not guaranteed that Thread #2
// got Thread #1's set.
}
}
Run Code Online (Sandbox Code Playgroud)
简而言之,问题volatile
在于,虽然它确实插入了MemoryBarrier()
,但在这种情况下它不会将它们插入我们需要它们的地方.
归档时间: |
|
查看次数: |
6904 次 |
最近记录: |