为什么要使用同步
int x = 0;
Parallel.For(0, 10000, (i) =>
{
x++;
x--;
});
Console.WriteLine(x);
输出
3
按理说应该输出0,但是由于多个线程访问了x,导致了竞态条件。
1、Monitor
)Monitor.Enter、Monitor.Exit
能够阻止多个线程同时访问特定的代码
readonly static object _sync = new object();
Parallel.For(0, 10000, (i) =>
{
try
{
//throw new Exception();//假设有异常
Monitor.Enter(_sync);
x++;
x--;
}
finally
{
Monitor.Exit(_sync);//这里也会有异常
}
});
Parallel.For(0, 10000, (i) =>
{
bool lockTaken = false;
try
{
Monitor.Enter(_sync, ref lockTaken);//假设获取锁失败
x++;
x--;
}
finally
{
if (lockTaken)
{
Monitor.Exit(_sync);//这里就不会进来
}
}
});
以上两种方法最终输出的结果都是0
2) Monitor.Wait、 Monitor.Pulse
在生产者消费者模式中,可以实现生产几个,就消费几个,不消费完,不生产,代码如下:
class MonitorSample
{
public int index=0;
Queue<string> m_smplQueue;
private int count = 0;
public MonitorSample()
{
m_smplQueue = new Queue<string>();
}
public void FirstThread()
{
while(true)
{
lock (m_smplQueue)
{
if (m_smplQueue.Count == 0)
{
m_smplQueue.Enqueue("面包");
Console.WriteLine("生产一个面包");
index++;
count++;
}
Monitor.Pulse(m_smplQueue);
}
if(count==10)
{
break;
}
}
}
public void SecondThread()
{
while (true)
{
lock (m_smplQueue)
{
if (m_smplQueue.Count > 0)
{
m_smplQueue.Dequeue();
Console.WriteLine("消费一个面包");
index--;
}
Monitor.Wait(m_smplQueue);
}
}
}
}
调用
//Create the MonitorSample object.
MonitorSample test = new MonitorSample();
//Create the first thread.
Thread tFirst = new Thread(new ThreadStart(test.FirstThread));
//Create the second thread.
Thread tSecond = new Thread(new ThreadStart(test.SecondThread));
//Start threads.
tFirst.Start();
tSecond.Start();
//wait to the tFirst thread
tFirst.Join();
Console.WriteLine(test.index);
输出
生产一个面包
消费一个面包
生产一个面包
消费一个面包
生产一个面包
消费一个面包
生产一个面包
消费一个面包
生产一个面包
消费一个面包
生产一个面包
消费一个面包
生产一个面包
消费一个面包
生产一个面包
消费一个面包
生产一个面包
消费一个面包
生产一个面包
消费一个面包
0
2、Interlocked类
1)Exchange方法,以原子操作的形式,将 32 位有符号整数设置为指定的值并返回原始值,将value的值赋值给location1,返回location1的原始值
public static int Exchange(
ref int location1,
int value
)
private static void MyThreadProc()
{
for (int i = 0; i < 1; i++)
{
UseResource();
//Wait 1 second before next attempt.
Thread.Sleep(1000);
}
}
static bool UseResource()
{
//0 indicates that the method is not in use.
if (0 == Interlocked.Exchange(ref usingResource, 1))
{
Console.WriteLine("{0} acquired the lock", Thread.CurrentThread.Name);
//Code to access a resource that is not thread safe would go here.
//Simulate some work
Thread.Sleep(500);
Console.WriteLine("{0} exiting lock", Thread.CurrentThread.Name);
//Release the lock
Interlocked.Exchange(ref usingResource, 0);
return true;
}
else
{
Console.WriteLine(" {0} was denied the lock", Thread.CurrentThread.Name);
return false;
}
}
调用:
Thread myThread;
Random rnd = new Random();
for (int i = 0; i < numThreads; i++)
{
myThread = new Thread(new ThreadStart(MyThreadProc));
myThread.Name = String.Format("Thread{0}", i + 1);
//Wait a random amount of time before starting next thread.
Thread.Sleep(rnd.Next(0, 1000));
myThread.Start();
}
输出
Thread1 acquired the lock
Thread1 exiting lock
Thread2 acquired the lock
Thread2 exiting lock
Thread3 acquired the lock
线程 0x41e4 已退出,返回值为 0 (0x0)。
Thread3 exiting lock
Thread4 acquired the lock
Thread5 was denied the lock
线程 0x4c6c 已退出,返回值为 0 (0x0)。
Thread4 exiting lock
线程 0x420 已退出,返回值为 0 (0x0)。
Thread6 acquired the lock
线程 0x1e1c 已退出,返回值为 0 (0x0)。
线程 0x4b0c 已退出,返回值为 0 (0x0)。
Thread6 exiting lock
Thread7 acquired the lock
Thread7 exiting lock
线程 0x2570 已退出,返回值为 0 (0x0)。
Thread8 acquired the lock
Thread8 exiting lock
Thread9 acquired the lock
线程 0x3aac 已退出,返回值为 0 (0x0)。
Thread10 was denied the lock
Thread9 exiting lock
2)CompareExchange方法比较两个 32 位有符号整数是否相等,如果相等,则替换第一个值。
public static int CompareExchange(
ref int location1,
int value,
int comparand
)
参数
location1
类型: System.Int32
其值将与 comparand 进行比较并且可能被替换的目标。
value
类型: System.Int32
比较结果相等时替换目标值的值。
comparand
类型: System.Int32
与位于 location1 处的值进行比较的值。
返回值
类型: System.Int32
location1 中的原始值。
public class ThreadSafe
{
// Field totalValue contains a running total that can be updated
// by multiple threads. It must be protected from unsynchronized
// access.
private float totalValue = 0.0F;
// The Total property returns the running total.
public float Total { get { return totalValue; } }
// AddToTotal safely adds a value to the running total.
public float AddToTotal(float addend)
{
float initialValue, computedValue;
do
{
// Save the current running total in a local variable.
initialValue = totalValue;
// Add the new value to the running total.
computedValue = initialValue + addend;
// CompareExchange compares totalValue to initialValue. If
// they are not equal, then another thread has updated the
// running total since this loop started. CompareExchange
// does not update totalValue. CompareExchange returns the
// contents of totalValue, which do not equal initialValue,
// so the loop executes again.
}
while (initialValue != Interlocked.CompareExchange(ref totalValue,
computedValue, initialValue));
// If no other thread updated the running total, then
// totalValue and initialValue are equal when CompareExchange
// compares them, and computedValue is stored in totalValue.
// CompareExchange returns the value that was in totalValue
// before the update, which is equal to initialValue, so the
// loop ends.
// The function returns computedValue, not totalValue, because
// totalValue could be changed by another thread between
// the time the loop ends and the function returns.
return computedValue;
}
}
private static void TestThread()
{
// Wait until the signal.
for (int i = 1; i <= 1; i++)
{
// Add to the running total in the ThreadSafe instance, and
// to an ordinary float.
//
float testValue = (float)r.NextDouble();
control += testValue;
ts.AddToTotal(testValue);
}
}
调用:
Thread t1 = new Thread(TestThread);
t1.Name = "Thread 1";
t1.Start();
Thread t2 = new Thread(TestThread);
t2.Name = "Thread 2";
t2.Start();
// Now let the threads begin adding random numbers to
// the total.
// Wait until all the threads are done.
t1.Join();
t2.Join();
Console.WriteLine("Thread safe: {0} Ordinary float: {1}",
ts.Total, control);
输出
Thread safe: 1.384963 Ordinary float: 1.384963
实现了多个线程对同一个变量的值进行更改。
3)Increment,Decrement,
Increment对变量进行原子性的加一;
Decrement对变量进行原子性的减一;
Parallel.For(0, 100000, new Action<int>((i) =>
{
Interlocked.Increment(ref count);
Interlocked.Decrement(ref count);
}));
Console.WriteLine(count);
输出
0
4)Add
对变量进行原子性的增加一个值
Parallel.For(0, 100000, new Action<int>((i) =>
{
Interlocked.Add(ref count, 5);
Interlocked.Add(ref count, -5);
}));
Console.WriteLine(count);
输出
0
3、重置事件类ManualResetEvent,ManualResetEventSlim
由于ManualResetEventSlim的性能优于ManualResetEvent,尽量使用ManualResetEventSlim
1) ManualResetEvent
ManualResetEvent manualReset = new ManualResetEvent(false );//将等待ManualResetEvent的线程设置为阻塞
Task.Run(new Action(() => {
while (true)
{
manualReset .WaitOne ();//阻止当前线程,直到当前 WaitHandle 收到信号
Console.WriteLine(“task3继续执行”);
manualReset.Reset();//阻塞线程
}
}));
Task.Run(new Action(() => { manualReset.Set(); }));//将等待ManualResetEvent的线程设置终止状态,线程不阻塞
2)ManualResetEventSlim
ManualResetEventSlim slim = new ManualResetEventSlim(false );
Task.Run(new Action(()=> { while (true)
{
slim.Wait();
Console.WriteLine("task1继续执行");
slim.Reset();
}
}));
Task.Run(new Action(() => {
while (true)
{
slim.Wait();
Console.WriteLine("task2继续执行");
slim.Reset();
}
}));
ManualResetEventSlim 的好处是可以同时阻塞几个线程,并且可以使用set同时让几个线程执行,但是Autoresetevent的set方法只能让一个线程执行。
4、信号量
1)Semaphore
Semaphore pool = new Semaphore(0,5);
for(int i=0;i<3;i++)
{
Task.Run(new Action (()=>
{
pool.WaitOne();
Console.WriteLine($"我是{Task.CurrentId }task");
Thread.Sleep(1000);
Console.WriteLine(pool.Release(2));
}));
}
Console.WriteLine( $"前一个计数为:{pool.Release(1)}" );
输出
前一个计数为:0//初始信号量计数为0,所以为0,同时释放一个
我是9task
0//本来释放了一个计数,但是执行完第一个Task使用了一个,所以为0,同时释放两个
我是10task
我是11task
0//又释放了两个,但是之前两个又被另外两个Task使用了,所以还是为0
2//又释放了两个,由于之前释放了两个,所以为2,并且自己又释放了两个
1)CountdownEvent(计数为0时,才允许线程继续执行)
CountdownEvent countDownEvent = new CountdownEvent(3);
for (int i = 0; i < 4; i++)
{
Task.Run(new Action(() =>
{
countDownEvent.Wait();
Console.WriteLine(Task.CurrentId);
}));
}
countDownEvent.Signal(3);
5、线程本地存储
ThreadLocal
ThreadLocal _Count = new ThreadLocal(()=>0.123);
public double Count
{
get
{
return _Count.Value ;
}
set
{
_Count.Value = value;
}
}
Parallel.For(0, 100000, new Action<int>((i) =>
{
Count++;
Count--;
}));
Console.WriteLine(_Count.Value);