C#线程同步

159 篇文章 168 订阅

为什么要使用同步

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);
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

c#上位机

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值