C#线程(二)线程同步

一、基本概念

当多个线程同时对一个资源进行操作的时候,便会引发问题,这个时候就需要线程同步,比较典型的就是多线程执行加减操作。
解决方式:

  1. 尽可能的重新设计代码逻辑,避免使用线程同步
  2. 若必须使用线程同步,就需要使用原子操作
    这里要介绍一个概念,**原子操作(atomic operation)**意为”不可被中断的一个或一系列操作”,也就是说原子操作是不需要考虑线程同步问题的,它在一个线程的操作执行完之前,不会跳转到另外一个线程
  3. 如果无法使用原子操作,那么我们可以用以下方式实现线程同步:
    1)内核模式
    即将等待的线程处于阻塞状态,这样它会占用CPU尽可能少的时间,但是在恢复线程的时候,需要切换一次上下文(context switch),这会消耗相当多的资源,一般只有这个线程会被挂起很久,我们才会这样做
    2)用户模式
    不阻塞线程,只是等待其他线程执行完毕,这样适合需要等待的时间比较少的线程,避免了上下文切换的资源消耗
    3)混合模式
    先使用用户模式,等待一定时间后,切换到内核模式,以节省资源

二、线程同步的实现

先介绍一下线程不同步的情况

		private static List nums;
        private static int count;
        static void Main(string[] args)
        {
            nums = new List();
            for (int i = 0; i < 20; i++)
            {
                nums.Add(i);
            }

            Thread t1 = new Thread(TestFun);
            t1.Name = "thread_1";
            Thread t2 = new Thread(TestFun);
            t2.Name = "thread_2";

            t1.Start();
            t2.Start();
            t1.Join();
            t2.Join();

            Console.ReadKey();
        }

        private static void TestFun()
        {
            while (nums.Count > 0)
            {
                Console.WriteLine("----------"+Thread.CurrentThread.Name + "   num:" + nums[0]+"-------------");
                Console.WriteLine(Thread.CurrentThread.Name + "  num:" + nums[0]+"  Enter");
                if (nums.Count > 0)
                {
                    Console.WriteLine(Thread.CurrentThread.Name + "  num:" + nums[0] + "  StartRemove");
                    nums.RemoveAt(0);
                }
                if (nums.Count > 0)
                {
                    Console.WriteLine(Thread.CurrentThread.Name + "  num:" + nums[0] + "  EndRemove");
                    Thread.Sleep(1);
                }
            }
        }

执行结果

1、Interlocked类

线程不同步示例:

		private static int count;
        static void Main(string[] args)
        {
            Thread t1 = new Thread(TestFun);
            t1.Name = "thread_1";
            Thread t2 = new Thread(TestFun);
            t2.Name = "thread_2";

            t1.Start();
            t2.Start();
            t1.Join();
            t2.Join();

            Console.ReadKey();
        }

        private static void TestFun()
        {
            while (count < 2)
            {
                count++;
                count--;
                Console.WriteLine(count);
                Thread.Sleep(1);
            }
        }

输出结果:
C#线程(二)线程同步_第1张图片
我们期望的结果,肯定是输出一直都是0,但是以上代码,在执行了一定次数后,就会发生问题

在线程的方法中使用++或者–之类的操作都是线程不安全的

以++为例,它实际上是三步操作:
1)从内存中获取值
2)改变值的大小
3)存回内存中
这三步都有可能被线程调度器打断

所以这个时候,需要使用线程安全的方式,即Interlocked类
Interlocked类为多个线程共享的变量提供原子操作
把上面代码中的++和–操作,换成

Interlocked.Increment(ref count);
Interlocked.Decrement(ref count);

就不会出现上面的问题了

2、Mutex(互斥)类

Mutex类:一个同步基元,也可用于进程间同步,这样只对一个线程授予对资源的独占访问

		static void Main(string[] args)
        {
            Thread t1 = new Thread(TestFun);
            t1.Name = "线程1";
            Thread t2 = new Thread(TestFun);
            t2.Name = "线程2";
            Thread t3 = new Thread(TestFun);
            t3.Name = "线程3";
            Thread t4 = new Thread(TestFun);
            t4.Name = "线程4";

            t1.Start();
            t2.Start();
            t3.Start();
            t4.Start();

            Console.ReadKey();
        }


        private static void TestFun()
        {
            bool isNew;

            using (Mutex m = new Mutex(false, "custom", out isNew))
            {
                Console.WriteLine(Thread.CurrentThread.Name + "  " + (isNew ? "获取到了互斥量" : "未获取到互斥量"));
                Thread.Sleep(1000);
            }
        }

C#线程(二)线程同步_第2张图片

3、SemaphoreSlim 类

SemaphoreSlim 类:对可同时访问资源或资源池的线程数加以限制

 private static SemaphoreSlim semaphore = new SemaphoreSlim(1);
        static void Main(string[] args)
        {
            Thread t1 = new Thread(TestFun);
            t1.Name = "线程1";
            Thread t2 = new Thread(TestFun);
            t2.Name = "线程2";

            t1.Start();
            t2.Start();

            Console.ReadKey();
        }


        private static void TestFun()
        {
            Console.WriteLine("{0}  Enter",Thread.CurrentThread.Name);
            semaphore.Wait();
            Console.WriteLine("{0}  Start", Thread.CurrentThread.Name);
            Thread.Sleep(2000);
            semaphore.Release();
            Console.WriteLine("{0}  End", Thread.CurrentThread.Name);
        }

我们这里限制了线程并发数为1,所以两个显示开始执行后,线程1开始执行逻辑,线程2在等待
C#线程(二)线程同步_第3张图片
两秒后线程1执行完毕,线程2开始执行
C#线程(二)线程同步_第4张图片
两秒后线程2执行完毕
C#线程(二)线程同步_第5张图片

都是一些预定义的类,实现线程同步的类还有很多,就不一一赘述了,这里就写几个比较基本的,来清晰概念。

我会在我的公众号上推送新的博文,也可以帮大家解答问题
微信公众号 Andy and Unity 搜索名称或扫描二维码
在这里插入图片描述
希望我们能共同成长,共同进步

你可能感兴趣的:(C#,C#基础及应用)