Skip to main content
 首页 » 编程设计

c#之无锁线程安全状态同步

2024年02月05日33shanyou

首先,我知道这样的问题:

reference assignment is atomic so why is Interlocked.Exchange(ref Object, Object) needed?

...但我仍然不确定我是否可以在我的案例中避免使用 lock(){}。

在我的例子中,我有一个代表某种状态的类,并且只有一个不时修改该状态的单一线程。虽然有许多线程读取状态。

在我的情况下,状态对象是否需要 Interlocked.Exchange()? 我绝对必须使用 lock(){}

这是我的示例代码,已简化为最低限度:

using System; 
using System.Collections.Generic; 
using System.Threading; 
using System.Threading.Tasks; 
 
namespace MultiThreadingExample 
{ 
class State 
{ 
    public int X { get; set; } 
    public string Str { get; set; } 
    public DateTime Current { get; set; } 
} 
 
class Example 
{ 
    State state; 
    CancellationTokenSource cts = new CancellationTokenSource(); 
 
    Task updater; 
    List<Task> readers = new List<Task>(); 
 
    public void Run() 
    { 
        updater = Task.Factory.StartNew(() => 
        { 
            while (!cts.Token.IsCancellationRequested) 
            { 
                // wait until we have a new state from some source 
                Thread.Sleep(1000); 
 
                var newState = new State() { Current = DateTime.Now, X = DateTime.Now.Millisecond, Str = DateTime.Now.ToString() }; 
 
                // critical part 
                state = newState; 
            } 
        }, cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default); 
 
        for (int i = 0; i < 10; i++) 
        { 
            readers.Add(Task.Factory.StartNew(() => 
            { 
                while (!cts.Token.IsCancellationRequested) 
                { 
                    // critical part 
                    var readState = state; 
 
                    // use it 
                    if (readState != null) 
                    { 
                        Console.WriteLine(readState.Current); 
                        Console.WriteLine(readState.Str); 
                        Console.WriteLine(readState.X); 
                    } 
                } 
            }, cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default)); 
        } 
    } 
} 
 
class Program 
{ 
    static void Main(string[] args) 
    { 
        new Example().Run(); 
        Console.ReadKey(); 
    } 
} 
} 

请您参考如下方法:

有疑问时使用锁

除非您实际上注意到性能问题或有一个部分可以被异步逻辑访问,该逻辑可能是也可能不是多线程的并且需要进行等待和发信号会推荐使用锁来实现线程间的同步。

话虽如此

可以使用 Thread.MemoryBarrier 来完成您的要求

只要您不关心所读取数据的准确性,您最终遇到的问题就与编译器优化有关因为它是多线程的,所以错误地重新排序了你的指令。

一种可以避免这种情况的方法是使用 Thread.MemoryBarrier 方法。

            while (!cts.Token.IsCancellationRequested) 
            { 
                //No command before the barrier 
                Thread.MemoryBarrier(); 
                //Can end up on this side of the barrier   
 
                // critical part 
                var readState = state; 
 
                // use it 
                if (readState != null) 
                { 
                    Console.WriteLine(readState.Current); 
                    Console.WriteLine(readState.Str); 
                    Console.WriteLine(readState.X); 
                } 
            } 

这叫做半围栏,这里有很多关于围栏的信息,解释得比我以前能解释的要好得多a free ebook, Threading in C# by Joseph Albahari