zoukankan      html  css  js  c++  java
  • 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....

  • 相关阅读:
    JavaSE 基础 第51节 定义自己的异常
    JavaSE 基础 第50节 Java中的异常链
    JavaSE 基础 第49节 手动抛出异常
    JavaSE 基础 第48节 Java中的异常声明
    JavaSE 基础 第47节 获取异常信息
    JavaSE 基础 第46节 异常的分类
    JavaSE 基础 第45节Java异常快速入门
    JavaSE 基础 第44节 引用外部类的对象
    JavaSE 基础 第43节 静态内部类
    通用爬虫
  • 原文地址:https://www.cnblogs.com/bruce1992/p/14349076.html
Copyright © 2011-2022 走看看