• C# 线程同步的多种方式


    实际应用中多个线程往往需要共享数据,因此必须使用同步技术,确保一次只有一个线程访问和改变共享数据。同步又分为进程内部线程的同步以及进程之间线程的同步。

    进程内部线程同步:

    1. lock : 使用比较简单 lock(obj){ Synchronize part  };  只能传递对象,无法设置等待超时;

    2. InterLocked:  原子操作,提供了以线程安全的方式递增,递减,交换和读取值的方法;

    3. Monitor: lock语句等同于Monitor.Enter() ,同样只能传递对象,无法设置等待超时,如下:

                Monitor.Enter(obj){
                    //Synchronized part
                }finally{
                    Monitor.Exit(obj);
                }

    另外使用Monitor.TryEnter(),可以传递等待超时,若获取锁,则布尔参考变量设为true,执行同步操作;若超时未获取锁,则布尔参考变量设为false,执行其他操作; 如下:

                bool lockTaken=false;
                Monitor.TryEnter(obj, 500, ref lockTaken);
                if(lockTaken){
                    try
                    {
                        //Synchronized part
                    }
                    finally
                    {
                        Monitor.Exit(obj);
                    }
                }else{
                    //don't aquire the lock, excute other parts
                }

     进程之间线程同步:

    1. WaitHandle: 一个抽象基类,用于等待一个信号的设置。 常用方法如下:

    WaitOne(): 等待一个信号的出现,可设置超时;

    WaitAll(): 等待多个信号的出现,可设置超时;

    WaitAny(): 等待任意一个信号的出现,可设置超时;

    Mutex类(Mutual Exclusion 互斥),EventWaitHandle类,Semaphore类 均派生自WaitHandle类。

    2. Mutex: 与Monitor 类似,只有一个线程能够获取锁定。利用WaitOne() 获取锁定,利用ReleaseMutex() 解除锁定。构造函数使用如下:

                bool isNew = false;
                mutex = new Mutex(false, "Mutex1", out isNew);

    参数1:锁创建后是否由主调线程拥有。 如果设为true,相当于调用了WaitOne(),需要释放,否则其他线程无法获取锁;

    参数2:锁名称,可通过OpenExist()或TryOpenExist() 打开已有锁,因为操作系统识别有名称的互锁,所以可由不同的进程共享。若锁名称为空,就是未命名的互锁,不能在多个进程之间共享;

    参数3:  是否为新创建的互锁;

    下面的例子演示Mutex 在进程之间的使用:

        class Program
        {
            private static Mutex mutex = null;  
            static void Main(string[] args)
            {
                bool isNew = false;
                mutex = new Mutex(false, "Mutex1", out isNew);
                Console.WriteLine("Main Start....");
                mutex.WaitOne();
                Console.WriteLine("Aquire Lock and Running....");
                Thread.Sleep(10000);
                mutex.ReleaseMutex();
                Console.WriteLine("Release Lock....");
                Console.WriteLine("Main end....");
                Console.ReadLine();
            }
        }

    连续2次运行这个控制台程序的exe,结果如下,首先运行的获取 Mutex1 互锁, 后面运行的会等待直到前面运行的释放 Mutex1 互锁。

     

     3.Semaphore: 信号量的作用于互斥锁类似,但它可以定义一定数量的线程同时使用。下面是构造函数:

                bool isNew = false;
                semaphore = new Semaphore(3, 3, "semaphore1", out isNew);

    参数1:创建后,最初释放的锁的数量,如参数1设为2,参数2设为3,则创建后只有2个锁可用,另1个已经锁定;

    参数2:定义可用锁的数量;

    参数3:  信号量的名称,与Mutex类似;

    参数4:否为新创建的互锁;

    以下例子创建了信号量“semaphore1”,利用Parallel.For() 同步运行Func1() ,在Func1() 中,当线程获取信号量锁,释放锁或等待超时,都会在控制台里输出,

    class Program
        {
            private static Semaphore semaphore = null;
            static void Main(string[] args)
            {
    
                Console.WriteLine("Main Start....");
                bool isNew = false;
                semaphore = new Semaphore(3, 3, "semaphore1", out isNew);
                Parallel.For(0, 6, Func1);
                Console.WriteLine("Main end....");
                Console.ReadLine();
            }
    
            static void Func1(int index)
            {
                Console.WriteLine("Task {0} Start....",Task.CurrentId);
                bool isComplete = false;
                while (!isComplete)
                {
                    if (semaphore.WaitOne(1000))    
                    {
                        try
                        {
                            Console.WriteLine("Task {0} aquire lock....", Task.CurrentId);
                            Thread.Sleep(5000);
                        }
                        finally
                        {
                            semaphore.Release();
                            Console.WriteLine("Task {0} release lock....", Task.CurrentId);
                            isComplete = true;
                        }
                    }
                    else
                    {
                        Console.WriteLine("Task {0} timeout....", Task.CurrentId);
                    }
                }
            }

    运行结果如下,线程1,2,3首先获取信号量锁,线程4,5,6在等待,直到1,2,3释放,

    Main Start....
    Task 1 Start....
    Task 1 aquire lock....
    Task 2 Start....
    Task 2 aquire lock....
    Task 3 Start....
    Task 3 aquire lock....
    Task 4 Start....
    Task 5 Start....
    Task 6 Start....
    Task 4 timeout....
    Task 5 timeout....
    Task 6 timeout....
    Task 5 timeout....
    Task 4 timeout....
    Task 6 timeout....
    Task 4 timeout....
    Task 5 timeout....
    Task 6 timeout....
    Task 4 timeout....
    Task 5 timeout....
    Task 6 timeout....
    Task 5 aquire lock....
    Task 1 release lock....
    Task 4 aquire lock....
    Task 6 aquire lock....
    Task 2 release lock....
    Task 3 release lock....
    Task 5 release lock....
    Task 4 release lock....
    Task 6 release lock....
    Main end....

     4. AutoResetEvent 类:可以使用事件通知其他任务,构造函数为 public AutoResetEvent(bool initialState)。

    当initialState=true,处于signaled 模式(终止状态),调用waitone() 也不会阻塞任务,等待信号,调用Reset()方法,可以设置为non-signaled 模式;

    当initialState=fasle,处于non-signaled 模式(非终止状态),调用waitone() 会等待信号阻塞当前线程(可以在多个线程中调用,同时阻塞多个线程),直到调用set()发送信号释放线程(调用一次,只能释放一个线程),一般使用这种方式;

    以下例子创建5个任务,分别调用waitone()阻塞线程,接着每隔2s 调用set(),

            private static AutoResetEvent autoReset = new AutoResetEvent(false);
            static void Main(string[] args)
            {
                Console.WriteLine("Main Start....");
                for (int i = 0; i < 5; i++)
                {
                    Task.Factory.StartNew(() =>
                    {
                        Console.WriteLine("{0} Start....", Task.CurrentId);
                        autoReset.WaitOne();
                        Console.WriteLine("{0} Continue....", Task.CurrentId);
                    });
                }
                for (int i = 0; i < 5;i++ )
                {
                    Thread.Sleep(2000);
                    autoReset.Set();
                }
                Console.WriteLine("Main end....");
                Console.ReadLine();
            }

    运行结果每次顺序略有不同,释放是随机的:

    Main Start....
    1 Start....
    2 Start....
    3 Start....
    4 Start....
    5 Start....
    3 Continue....
    1 Continue....
    4 Continue....
    2 Continue....
    Main end....
    5 Continue....

     5. ManualResetEvent 类:功能基本上和AutoSetEvent类似,但又一个不同点:

    使用AutoSetEvent,每次调用set(),切换到终止模式,只能释放一个waitone(),便会自动切换到非终止模式;但ManualResetEvent,调用set(),切换到终止模式,可以释放当前所有的waitone(),需要手动调用reset()才能切换到非终止模式。

    以下例子说明了这个不同的:

            private static ManualResetEvent manualReset = new ManualResetEvent(false);
            static void Main(string[] args)
            {
                Console.WriteLine("Main Start....");
                for (int i = 0; i < 5; i++)
                {
                    Task.Factory.StartNew(() =>
                    {
                        Console.WriteLine("{0} Start....", Task.CurrentId);
                        manualReset.WaitOne();
                        Console.WriteLine("{0} Continue....", Task.CurrentId);
                    });
                }
                Thread.Sleep(2000);
                manualReset.Set();
                manualReset.WaitOne();
                Console.WriteLine("it doesn't work now, Main continue....");
                manualReset.Reset();
                manualReset.WaitOne();
                Console.WriteLine("Main end....");
                Console.ReadLine();
            }

    运行结果:

    Main Start....
    1 Start....
    2 Start....
    3 Start....
    4 Start....
    5 Start....
    5 Continue....
    4 Continue....
    3 Continue....
    2 Continue....
    it doesn't work now, Main continue....
    1 Continue....

  • 相关阅读:
    Pytest 系列(28)- 参数化 parametrize + @allure.title() 动态生成标题
    Pytest 系列(27)- allure 命令行参数
    Pytest 系列(26)- 清空 allure 历史报告记录
    Pytest 系列(25)- 标记用例级别 @allure.
    Pytest 系列(24)- allure 环境准备
    基于Python的三种Bandit算法的实现
    博客迁移
    团体程序设计天梯赛2020游记
    P1825 [USACO11OPEN]Corn Maze S
    # JavaScript中的对象转数组Array.prototype.slice.call()方法详解
  • 原文地址:https://www.cnblogs.com/change-myself/p/11204573.html
Copyright © 2020-2023  润新知