目录
三、避免锁定 this、typeof(type)和 string
(3)重置事件类:ManualResetEvent和ManualResetEventSlim
(4)Semaphore / SemaphoreSlim 和 CountdownEvent
线程同步的作用是避免死锁的同时防止出现竞态条件。若能同步多个线程对代码或数据的并发访问,就说这些代码和数据是线程安全的。
关于变量读写的原子性,有一个重点需要注意:假如类型的大小不超过一个本机(指针大小的)整数,“运行时”就保证该类型不会被部分性地读取或写入。所以,64位操作系统保证能够原子性地读写一个long (64位)。然而,128位变量(比如decimal)的读写就不保证是原子性的。所以,通过写操作来更改一个decimal变量时,可能会在仅仅复制了32位之后被打断,造成以后读取一个不正确的值,这称为一次torn read (被撕裂的读取)。
局部变量没有必要同步。局部变量加载到栈上,而每个线程都有自己的逻辑栈。所以,针对每个方法调用,每个局部变量都有自己的实例。在不同的方法调用之间,局部变量默认是不共享的。因此,它们在多个线程之间也是不共享的。然而,这并不是说局部变量完全没有并发性问题,因为代码可能轻易向多个线程公开局部变量”。例如,在迭代之间共享局部变量的并行for循环就会公开变量,使其可能被并发访问,从而造成一个竞态条件。s
public static void Main()
{
int x = 0;
Parallel.For(0, 10000, i => { x++; x--; });
Console.WriteLine($"Count={x}");
}
使用await/async模式重构代码
public static async void CountAsync()
{
int count = 0;
Task task = Task.Run(() =>
{
for(int i=0;i<1000; i++)
{
count++;
count--;
}
});
await task;
Console.WriteLine($"Count2={count}");
}
Count1=17
Count2=0
Count1=108
Count2=0
一、使用Monitor来同步
为了同步多个线程,防止它们同时执行特定的代码段,需要用监视器(monitor)来阻止第二个线程进入受保护的代码段,直到第一个线程退出那个代码段。监视器功能由System.Threading.Monitor类提供。为了标识受保护代码段的开始和结束位置,需要分别调用静态方法Monitor.Enter()和Monitor.Exit()。
readonly static object sync=new object();
const int total=1000;
static long count=0;
public static void Main()
{
Task task = Task.Run(() => Decrement());
for (int i = 0; i < total; i++)
{
bool lockTaken = false;
try
{
Monitor.Enter(sync,ref lockTaken);
count++;
}
finally
{
if(lockTaken)
{
Monitor.Exit(sync);
}
}
}
task.Wait();
Console.WriteLine($"Count={count}");
}
private static void Decrement()
{
for(int i = 0; i < total; i++)
{
bool lockToken=false;
try
{
Monitor.Enter(sync,ref lockToken);
count--;
}
finally
{
if(lockToken)
{
Monitor.Exit(sync);
}
}
}
}
Monitor还支持Pulse()方法,允许线程进入“就绪队列” (ready queue),指出下一个就轮到它获得锁。这是同步生产者一消费者模式的一种常见方式,目的是保证除非有“生产”,否则就没有“消费”。拥有监视器的生产者线程调用 Monitor.Pulse() 通知消费者线程一个项(item)已生产好,请“准备” (get ready)消费。
一个Pulse()调用只允许一个线程(消费者)进入就绪队列。生产者线程调用Monitor.Exit() 时,消费者线程将取得锁并进入关键执行区域以开始“消费”。消费者处理好等待处理的项以后,就调用Exit(),从而允许生产者(当前正由Monitor.Enter()阻塞)再次生产其他项。在本例中,一次只有一个线程进入就绪队列,确保没有“生产”就没有“消费”,反之亦然。
二、使用lock来同步
readonly static object sync=new object();
const int total=1000;
static long count=0;
public static void Main()
{
Task task = Task.Run(() => Decrement());
for (int i = 0; i < total; i++)
{
lock (sync)
{
count++;
}
}
task.Wait();
Console.WriteLine($"Count={count}");
}
private static