为什么使用 CancellationTokenSource.Cancel 的多线程代码需要较少的反重排序措施
Why does multithreaded code using CancellationTokenSource.Cancel require less anti-reordering measures
我想了解为什么共享 CancellationTokenSource
变量在这里不受锁或内存屏障的保护。
我知道有一条经验法则,如果允许编译器优化,则可以使用局部变量读取和写入对共享(状态)变量的读取或写入进行重新排序。
这是来自 CancellationTokenSource
documentation.
的示例
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
public class Example
{
public static void Main()
{
// Define the cancellation token.
CancellationTokenSource source = new CancellationTokenSource();
CancellationToken token = source.Token;
Random rnd = new Random();
Object lockObj = new Object();
List<Task<int[]>> tasks = new List<Task<int[]>>();
TaskFactory factory = new TaskFactory(token);
for (int taskCtr = 0; taskCtr <= 10; taskCtr++)
{
int iteration = taskCtr + 1;
tasks.Add(factory.StartNew(() =>
{
int value;
int[] values = new int[10];
for (int ctr = 1; ctr <= 10; ctr++)
{
lock (lockObj)
{
value = rnd.Next(0, 101);
}
if (value == 0)
{
source.Cancel();
Console.WriteLine("Cancelling at task {0}", iteration);
break;
}
values[ctr - 1] = value;
}
return values;
}, token));
}
try
{
Task<double> fTask = factory.ContinueWhenAll(tasks.ToArray(), (results) =>
{
Console.WriteLine("Calculating overall mean...");
long sum = 0;
int n = 0;
foreach (var t in results)
{
foreach (var r in t.Result)
{
sum += r;
n++;
}
}
return sum / (double)n;
}, token);
Console.WriteLine("The mean is {0}.", fTask.Result);
}
catch (AggregateException ae)
{
foreach (Exception e in ae.InnerExceptions)
{
if (e is TaskCanceledException)
Console.WriteLine("Unable to compute mean: {0}",
((TaskCanceledException)e).Message);
else
Console.WriteLine("Exception: " + e.GetType().Name);
}
}
finally
{
source.Dispose();
}
}
}
具体原因是什么? Microsoft 是否暗示这样的重新排序是安全的,因此不需要保护措施,或者根本不可能重新排序?
正如 The Old New Thing
的作者在他的评论中指出的那样,放置在多线程代码中的 source.Cancel();
指令通过其内部实现来防止重新排序。
https://referencesource.microsoft.com/#mscorlib/system/threading/CancellationTokenSource.cs,723 声明 CancellationTokenSource 依赖于 Interlocked class 方法。
根据 Joe Albahari 的说法,C# 中 Interlocked class 上的所有方法都隐式生成完整的栅栏:http://www.albahari.com/threading/part4.aspx#_Memory_Barriers_and_Volatility
因此,如果他们需要在多个任务访问时保护它,则可以在委托体内自由调用 CancellationTokenSource.Cancel 方法,而无需额外的锁或内存屏障。
我想了解为什么共享 CancellationTokenSource
变量在这里不受锁或内存屏障的保护。
我知道有一条经验法则,如果允许编译器优化,则可以使用局部变量读取和写入对共享(状态)变量的读取或写入进行重新排序。
这是来自 CancellationTokenSource
documentation.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
public class Example
{
public static void Main()
{
// Define the cancellation token.
CancellationTokenSource source = new CancellationTokenSource();
CancellationToken token = source.Token;
Random rnd = new Random();
Object lockObj = new Object();
List<Task<int[]>> tasks = new List<Task<int[]>>();
TaskFactory factory = new TaskFactory(token);
for (int taskCtr = 0; taskCtr <= 10; taskCtr++)
{
int iteration = taskCtr + 1;
tasks.Add(factory.StartNew(() =>
{
int value;
int[] values = new int[10];
for (int ctr = 1; ctr <= 10; ctr++)
{
lock (lockObj)
{
value = rnd.Next(0, 101);
}
if (value == 0)
{
source.Cancel();
Console.WriteLine("Cancelling at task {0}", iteration);
break;
}
values[ctr - 1] = value;
}
return values;
}, token));
}
try
{
Task<double> fTask = factory.ContinueWhenAll(tasks.ToArray(), (results) =>
{
Console.WriteLine("Calculating overall mean...");
long sum = 0;
int n = 0;
foreach (var t in results)
{
foreach (var r in t.Result)
{
sum += r;
n++;
}
}
return sum / (double)n;
}, token);
Console.WriteLine("The mean is {0}.", fTask.Result);
}
catch (AggregateException ae)
{
foreach (Exception e in ae.InnerExceptions)
{
if (e is TaskCanceledException)
Console.WriteLine("Unable to compute mean: {0}",
((TaskCanceledException)e).Message);
else
Console.WriteLine("Exception: " + e.GetType().Name);
}
}
finally
{
source.Dispose();
}
}
}
具体原因是什么? Microsoft 是否暗示这样的重新排序是安全的,因此不需要保护措施,或者根本不可能重新排序?
正如 The Old New Thing
的作者在他的评论中指出的那样,放置在多线程代码中的 source.Cancel();
指令通过其内部实现来防止重新排序。
https://referencesource.microsoft.com/#mscorlib/system/threading/CancellationTokenSource.cs,723 声明 CancellationTokenSource 依赖于 Interlocked class 方法。
根据 Joe Albahari 的说法,C# 中 Interlocked class 上的所有方法都隐式生成完整的栅栏:http://www.albahari.com/threading/part4.aspx#_Memory_Barriers_and_Volatility
因此,如果他们需要在多个任务访问时保护它,则可以在委托体内自由调用 CancellationTokenSource.Cancel 方法,而无需额外的锁或内存屏障。