• C#之线程同步


    简述

    当一个线程执行递增和递减操作时,其他线程需要依次等待,类似于这种常见的问题通常被称为线程同步问题。

    有多种方式实现线程同步。首先,如果无须共享对象,那么就无序进行线程同步。大多数时候,可以通过重新设计程序来移除共享状态,从而去掉重复的同步构造。

    如果必须使用共享的状态,第二种方式是只使用原子操作。这意味着一个操作只占用一个量子的时间,一次就可以完成。所以只有当前操作完成后,其他线程才能执行其他操作。

    如果上面的方式都不可行,并且程序的逻辑更加复杂,那么不得不使用不同的方式来协调线程。

    • 可以将等待的线程置于阻塞状态。当线程处于阻塞状态时,只会占用尽可能少的CPU时间。然而,这意味着将引入至少有一次所谓的上下文切换(Contex Switch)。上下文切换是指操作系统的县城刚刚调度器,该调度器会保存等待的线程的状态,并切换到另一个线程,依次恢复等待的线程的状态,这需要消耗相当多的资源。然而,如果线程要挂起很长时间,那么这么做是值得的。这种方式又称为内核模式,因为只有操作系统的内核才能阻止线程使用CPU时间。
    • 万一线程只需要等待一小段时间,最好只是简单的等待,而不用将线程切换到阻塞状态,虽然线程等待时,会浪费CPU时间,但是我们节省了上下文切换所消耗的CPU时间。该方式又称为用户模式。该方式非常轻量,速度很快,但如果线程需要等待较长时间,则会浪费大量的CPU时间。
    • 为了利用好这两种方式,可以使用混合模式,该模式会首先尝试使用用户模式等待,如果线程等待了足够长的时间,则会切换到内核模式,以节省CPU时间。

    执行基本的原子操作

    using System;
    using System.Threading;
    using System.Threading.Tasks;
    using  System.Diagnostics;
    namespace threadDemo
    {
        abstract class CounterBase
        {
            public abstract void Increment();
            public abstract void Decrement();
        }
        class Counter : CounterBase
        {
            private int _count;
            public int Count => _count;
            public override void Decrement()
            {
                _count--;
            }
    
            public override void Increment()
            {
                _count++;
            }
        }
        class CounterNoLock : CounterBase
        {
            private int _count;
            public int Count => _count;
            public override void Decrement()
            {
                Interlocked.Decrement(ref _count);
            }
    
            public override void Increment()
            {
                Interlocked.Increment(ref _count);
            }
        }
        class Program
        {
            static void TestCounter(CounterBase c)
            {
                for(int i = 0; i < 100000; i++)
                {
                    c.Increment();
                    c.Decrement();
                }
            }
           
           
            public static void Main(string[] args)
            {
                Console.WriteLine("Incorrect counter..");
                var c = new Counter();
                var t1 = new Thread(() => TestCounter(c));
                var t2 = new Thread(() => TestCounter(c));
                var t3 = new Thread(() => TestCounter(c));
                t1.Start();
                t2.Start();
                t3.Start();
                t1.Join();
                t2.Join();
                t3.Join();
                Console.WriteLine($"Total count:{c.Count}");
                Console.WriteLine("-".PadLeft(50, '-'));
                Console.WriteLine("Correct counter");
                var c1 = new CounterNoLock();
                t1 = new Thread(() => TestCounter(c1));
                t2 = new Thread(() => TestCounter(c1));
                t3 = new Thread(() => TestCounter(c1));
                t1.Start();
                t2.Start();
                t3.Start();
                t1.Join();
                t2.Join();
                t3.Join();
                Console.WriteLine($"Total count:{c1.Count}");
            }
        }
    }
    
    

    output:

    Incorrect counter..
    Total count:16
    --------------------------------------------------
    Correct counter
    Total count:0
    

    借助Interlocked类,我们无需锁定任何对象即可获取正确的结果,Interlocked提供了Increment,Decrement,Add等基本的数学操作的原子方法,从而帮助在编写Counter类时无需使用锁。

    使用Mutex类

    Mutex是.NET Framework中提供跨多个进程同步 访问的一个类。它非常类似于Monitor类,因为它们都只有有一个线程能拥有锁定。只有一个线程能获得互斥锁定,访问受互斥保护的同步代码区域。

    在mutex类的构造函数中,可以指定互斥是否最初由主调线程拥有,定义互斥的名称,获得互斥是否已存在的信息。

    bool createdNew;
    mutex mutex=new Mutex(false,"ProCSharp",out createdNew);
    

    上面代码中,第三个参数定义为输出参数,接受一个表示互斥是否为新建的布尔值。如果返回的值得是false,就表示互斥已经定义。互斥可以在另一个进程中定义,因为操作系统能够识别有名称的互斥,它由不同的进程共享,如果没有给互斥指定名称,互斥就是未命名的,不在不同的进程之间共享

    更详细的关于Mutex的用法详见:Mutex Class (System.Threading) | Microsoft Docs

    using System;
    using System.Threading;
    using System.Threading.Tasks;
    using  System.Diagnostics;
    namespace threadDemo
    {
        abstract class CounterBase
        {
            public abstract void Increment();
            public abstract void Decrement();
        }
        class Counter : CounterBase
        {
            private int _count;
            public int Count => _count;
            public override void Decrement()
            {
                _count--;
            }
    
            public override void Increment()
            {
                _count++;
            }
        }
        class CounterNoLock : CounterBase
        {
            private int _count;
            public int Count => _count;
            public override void Decrement()
            {
                Interlocked.Decrement(ref _count);
            }
    
            public override void Increment()
            {
                Interlocked.Increment(ref _count);
            }
        }
        class Program
        {
            static void TestCounter(CounterBase c)
            {
                for(int i = 0; i < 100000; i++)
                {
                    c.Increment();
                    c.Decrement();
                }
            }
           
           
            public static void Main(string[] args)
            {
                const string MutexName = "CSharpThreadCoding";
                using(var m=new Mutex(false, MutexName))
                {
                    if (!m.WaitOne(TimeSpan.FromSeconds(5), false))
                    {
                        Console.WriteLine("Second instance is running");
                    }
                    else
                    {
                        Console.WriteLine("Running");
                        Console.ReadLine();
                        m.ReleaseMutex();
                    }
                }
            }
        }
    }
    
    

    不按任何键,所以第一个进程不释放该互斥量,然后再运行第二个进程,则在5s内尝试获取该互斥量,获取不到,则m.WaitOne返回false

    第一个进程按下键后,释放互斥量,第二个

    Mutex类派生自基类WaitHandle,因此可以利用WaitOne方法来获得互斥锁定.

    • 对于WaitOne()方法:通过该方法,可以得知互斥量是否被拥有,如果互斥量正在被某一线程拥有,访问被保护的资源,则该方法返回false,如果互斥量没有被拥有,则该方法返回true,访问结束后,必须调用ReleaseMutex方法。
    • 对于waitOne(TimeSpan,bool),带有时间间隔的等待。

    MSDN文档的例子:

    using System;
    using System.Threading;
    
    class Example
    {
        // Create a new Mutex. The creating thread does not own the mutex.
        private static Mutex mut = new Mutex();
        private const int numIterations = 1;
        private const int numThreads = 3;
    
        static void Main()
        {
            // Create the threads that will use the protected resource.
            for(int i = 0; i < numThreads; i++)
            {
                Thread newThread = new Thread(new ThreadStart(ThreadProc));
                newThread.Name = String.Format("Thread{0}", i + 1);
                newThread.Start();
            }
    
            // The main thread exits, but the application continues to
            // run until all foreground threads have exited.
        }
    
        private static void ThreadProc()
        {
            for(int i = 0; i < numIterations; i++)
            {
                UseResource();
            }
        }
    
        // This method represents a resource that must be synchronized
        // so that only one thread at a time can enter.
        private static void UseResource()
        {
            // Wait until it is safe to enter.
            Console.WriteLine("{0} is requesting the mutex", 
                              Thread.CurrentThread.Name);
            mut.WaitOne();
    
            Console.WriteLine("{0} has entered the protected area", 
                              Thread.CurrentThread.Name);
    
            // Place code to access non-reentrant resources here.
    
            // Simulate some work.
            Thread.Sleep(500);
    
            Console.WriteLine("{0} is leaving the protected area", 
                Thread.CurrentThread.Name);
    
            // Release the Mutex.
            mut.ReleaseMutex();
            Console.WriteLine("{0} has released the mutex", 
                Thread.CurrentThread.Name);
        }
    }
    // The example displays output like the following:
    //       Thread1 is requesting the mutex
    //       Thread2 is requesting the mutex
    //       Thread1 has entered the protected area
    //       Thread3 is requesting the mutex
    //       Thread1 is leaving the protected area
    //       Thread1 has released the mutex
    //       Thread3 has entered the protected area
    //       Thread3 is leaving the protected area
    //       Thread3 has released the mutex
    //       Thread2 has entered the protected area
    //       Thread2 is leaving the protected area
    //       Thread2 has released the mutex
    

    最后建议,使用using代码块包裹互斥量

    使用SemaphoreSlim

    信号量非常类似于互斥,其区别在于:信号量可以同时由多个线程使用。信号量是一种计数的互斥锁定。使用信号量,可以定义允许同时访问受旗语锁定保护的资源的线程个数。如果需要限制可以访问可用资源的线程数,信号量就很有用。例如,如果系统有3个物理端口可用,就允许3个线程同时访问I/O端口,但第四个线程需要等待前三个线程中的一个释放资源。

    Semaphore类可以命名,使用系统范围内的资源,允许在不同进程之间同步,SemaphoreSlim类是对较短等待时间进行了优化的轻型版本,不使用Windows内核信号量,而且也不支持进程间同步,所以在跨程序同步的场景下可以使用Semaphore.

    class Program
        {
            static SemaphoreSlim _semaphore = new SemaphoreSlim(4);
            static void AccessDatabase(string name,int seconds)
            {
                Console.WriteLine($"{name} waits to access a database");
                _semaphore.Wait();
                Console.WriteLine($"{name} was granted an access to a database");
                Thread.Sleep(TimeSpan.FromSeconds(seconds));
                Console.WriteLine($"{name} is completed");
                _semaphore.Release();
            }
           
           
            public static void Main(string[] args)
            {
                for(int i = 1; i <= 6; i++)
                {
                    string threadName = "Thread" + i;
                    int secondsToWait = 2 + 2 * i;
                    var t = new Thread(() => AccessDatabase(threadName, secondsToWait));
                    t.Start();
                }
            }
        }
    

    output

    Thread1 waits to access a database
    Thread5 waits to access a database
    Thread5 was granted an access to a database
    Thread6 waits to access a database
    Thread6 was granted an access to a database
    Thread3 waits to access a database
    Thread3 was granted an access to a database
    Thread2 waits to access a database
    Thread4 waits to access a database
    Thread1 was granted an access to a database
    Thread1 is completed
    Thread4 was granted an access to a database
    Thread3 is completed
    Thread2 was granted an access to a database
    Thread5 is completed
    Thread6 is completed
    Thread2 is completed
    Thread4 is completed
    

    借助信号系统限制了访问数据库的并发数为4个线程,当有4个线程获取了数据库的访问后,其他两个线程需要等待,直到之前线程中的某一个完成工作并调用_semaphore.Release方法来发出信号。

    使用AutoResetEvent

    借助于AutoResetEvent类来从一个线程向另一个线程发送通知。代表着一个线程事件同步,在被signaled后(被调用了自身的Set方法),在释放了单个等待线程后,自动的进行了Reset(即变为unsignaled状态)。

    class Program
        {
            private static AutoResetEvent _workerEvent = new AutoResetEvent(false);
            private static AutoResetEvent _mainEvent = new AutoResetEvent(false);
    
            static void Process(int seconds)
            {
                Console.WriteLine("Starting a  loong running work...");
                Thread.Sleep(TimeSpan.FromSeconds(seconds));
                Console.WriteLine("Work is done!");
                _workerEvent.Set();
                Console.WriteLine("Waiting ffor a main thread to complete its work");
                _mainEvent.WaitOne();
                Console.WriteLine("Starting a second operation");
                Thread.Sleep(TimeSpan.FromSeconds(seconds));
                Console.WriteLine("Work again is done!");
                _workerEvent.Set();
            }
           
           
            public static void Main(string[] args)
            {
                var t = new Thread(() => Process(10));
                t.Start();
                Console.WriteLine("Waiting for another thread to complete work");
                _workerEvent.WaitOne();
                Console.WriteLine("Firtst operation is completed");
                Console.WriteLine("Performating an operation on a main thread");
                Thread.Sleep(TimeSpan.FromSeconds(5));
                _mainEvent.Set();
                Console.WriteLine("Now running the second operation on a secoond thread");
                _workerEvent.WaitOne();
                Console.WriteLine("Second operation is completed");
            }
        }
    

    output

    Waiting for another thread to complete work
    Starting a  loong running work...
    Work is done!
    Waiting ffor a main thread to complete its work
    Firtst operation is completed
    Performating an operation on a main thread
    Now running the second operation on a secoond thread
    Starting a second operation
    Work again is done!
    Second operation is completed
    

    当主程序启动时,定义了两个AutoResetEvent实例,其中一个是从子线程向主线程发信号,另一个是从

    主线程向子线程发送信号。我们向AutoResetEvent构造方法传入false,定义了这两个实例的初始状态为unsignaled,这意味着任何线程调用这两个对象的任何一个的WaitOne方法将会被阻塞,直到我们调用了相应的Set方法。

    AutoResetEvent类采用的是内核时间模式,所以等待时间不能太长。用ManualResetEventSlim类更好,因为它是混合模式。

    当上述代码的子线程数量为2个:

    class Program
        {
            private static AutoResetEvent _workerEvent = new AutoResetEvent(false);
            private static AutoResetEvent _mainEvent = new AutoResetEvent(false);
    
            static void Process(int seconds)
            {
                Console.WriteLine("Starting a  loong running work...");
                Thread.Sleep(TimeSpan.FromSeconds(seconds));
                Console.WriteLine("Work is done!");
                _workerEvent.Set();
                Console.WriteLine("Waiting ffor a main thread to complete its work");
                _mainEvent.WaitOne();
                Console.WriteLine("Starting a second operation");
                Thread.Sleep(TimeSpan.FromSeconds(seconds));
                Console.WriteLine("Work again is done!");
                _workerEvent.Set();
            }
    
    
            public static void Main(string[] args)
            {
                var t = new Thread(() => Process(10));
                var t1 = new Thread(() => Process(10));
                t1.Start();
                t.Start();
                Console.WriteLine("Waiting for another thread to complete work");
                _workerEvent.WaitOne();
                Console.WriteLine("Firtst operation is completed");
                Console.WriteLine("Performating an operation on a main thread");
                Thread.Sleep(TimeSpan.FromSeconds(5));
                _mainEvent.Set();
                Console.WriteLine("Now running the second operation on a secoond thread");
                _workerEvent.WaitOne();
                Console.WriteLine("Second operation is completed");
            }
        }
    

    output:

    Waiting for another thread to complete work
    Starting a  loong running work...
    Starting a  loong running work...
    Work is done!
    Work is done!
    Waiting ffor a main thread to complete its work
    Waiting ffor a main thread to complete its work
    Firtst operation is completed
    Performating an operation on a main thread
    Now running the second operation on a secoond thread
    Second operation is completed
    Starting a second operation
    Work again is done!
    

    发现只有一个Starting a second operation,因为两个子线程,只有一个进入了这个环节,所以,AutoResetEvent事件就像一个旋转门,一次只允许一个人通过,而下面的ManualResetEventSlim,则不然,是通过手动来设置SetReset的,所以更像一个大门,一直保持敞开,直到手动调用Reset才关闭,这期间,凡是可以进入的子线程都可以进入。

    ManualRestEventSlim

    class Program
        {
            static ManualResetEventSlim _mainEvent = new ManualResetEventSlim(false);
            static void TravelThroughGates(string threadName,int seconds)
            {
                Console.WriteLine($"{threadName} falls to sleep");
                Thread.Sleep(TimeSpan.FromSeconds(seconds));
                Console.WriteLine($"{threadName}  waits for the gate to open");
                _mainEvent.Wait();
                Console.WriteLine($"{threadName} enters the gates");
            }
            public static void Main(string[] args)
            {
                var t1 = new Thread(() => TravelThroughGates("Thread 1", 5));
                var t2 = new Thread(() => TravelThroughGates("Thread 2", 6));
                var t3 = new Thread(() => TravelThroughGates("Thread 3", 12));
                t1.Start();
                t2.Start();
                t3.Start();
                Thread.Sleep(TimeSpan.FromSeconds(6));
                Console.WriteLine("The gates are open now");
                _mainEvent.Set();
                Thread.Sleep(TimeSpan.FromSeconds(2));
                _mainEvent.Reset();
                Console.WriteLine("The gates have been closed");
                Thread.Sleep(TimeSpan.FromSeconds(10));
                Console.WriteLine("The gates are now open for the second time");
                _mainEvent.Set();
                Thread.Sleep(TimeSpan.FromSeconds(2));
                Console.WriteLine("The gates have been closed");
                _mainEvent.Reset();
            }
        }
    

    output:

    Thread 1 falls to sleep
    Thread 2 falls to sleep
    Thread 3 falls to sleep
    Thread 1  waits for the gate to open
    Thread 2  waits for the gate to open
    The gates are open now
    Thread 2 enters the gates
    Thread 1 enters the gates
    The gates have been closed
    Thread 3  waits for the gate to open
    The gates are now open for the second time
    Thread 3 enters the gates
    The gates have been closed
    

    使用CountDownEvent

    class Program
        {
    
            static CountdownEvent _countdown = new CountdownEvent(2);//The number of signals initially required to set the CountdownEvent.
            static void PerformOperation(string message,int seconds)
            {
                Thread.Sleep(TimeSpan.FromSeconds(seconds));
                Console.WriteLine(message);
                _countdown.Signal();//Registers a signal with the CountdownEvent, decrementing the value of CurrentCount.
            }
            public static void Main(string[] args)
            {
                Console.WriteLine("Starting two operaions");
                var t1 = new Thread(() => PerformOperation("Operation 1 is completted", 4));
                var t2 = new Thread(() => PerformOperation("Operation 2 is completed", 8));
                t1.Start();
                t2.Start();
                _countdown.Wait();
                Console.WriteLine("Both operations have been completed");
                _countdown.Dispose();
            }
        }
    

    output:

    Starting two operaions
    Operation 1 is completted
    Operation 2 is completed
    Both operations have been completed
    

    本代码中,CountdownEvent构造函数中指定了需要发出两次信号。然后我们启动了两个线程,当它们执行完任务后会发出信号,一旦第二个线程完成,主线程会从等待的CountdownEvent的状态中返回并继续执行,针对需要等待多个异步操作完成的情形,使用该方式是非常便利的。

    然而,特别需要注意的是:**如果调用_countdown.Signal()没达到指定的次数,那么_countdown.Wait()将一直等待,需要确保使用CountdownEvent时,所有线程完成后都要调用Signal方法。

    使用Barrier

    class Program
        {
            static Barrier _barrier = new Barrier(2, b => Console.WriteLine($"End of phase {b.CurrentPhaseNumber + 1}"));
            
            static void PlayMusic(string name,string message,int seconds)
            {
                for(int i = 1; i < 3; i++)
                {
                    Console.WriteLine("-".PadLeft(50, '-'));
                    Thread.Sleep(TimeSpan.FromSeconds(seconds));
                    Console.WriteLine($"{name} starts to {message}");
                    Thread.Sleep(TimeSpan.FromSeconds(seconds));
                    Console.WriteLine($"{name} finishes to {message}");
                    _barrier.SignalAndWait();
                }
            }
            public static void Main(string[] args)
            {
                var t1 = new Thread(() => PlayMusic("The guitarist", "play an amazing solo",5));
                var t2 = new Thread(() => PlayMusic("The singer", "sing his song", 2));
                t1.Start();t2.Start();
            }
        }
    

    output

    --------------------------------------------------
    --------------------------------------------------
    The singer starts to sing his song
    The singer finishes to sing his song
    The guitarist starts to play an amazing solo
    The guitarist finishes to play an amazing solo
    End of phase 1
    --------------------------------------------------
    --------------------------------------------------
    The singer starts to sing his song
    The singer finishes to sing his song
    The guitarist starts to play an amazing solo
    The guitarist finishes to play an amazing solo
    End of phase 2
    

    每个线程会向Barrier发送两次信号,因为for循环中,循环两次,所以有两个阶段,每次这两个线程调用SignalAndWait,所有参与线程都发出这个信号后,Barrier就执行回调函数。这在多线程迭代计算中非常有用,可以在每个迭代结束前执行一些运算

    将上述代码中的_barrier的构造函数中的2改为3,最后output如下:

    --------------------------------------------------
    --------------------------------------------------
    The singer starts to sing his song
    The singer finishes to sing his song
    The guitarist starts to play an amazing solo
    The guitarist finishes to play an amazing solo
    

    这是因为构造函数第一个参数为3,标明参与的线程一共3个,第二个参数postPhaseAction委托在所有参与线程到达边界(Barrier)后才执行,实际只有2个线程参加,所以程序会一直在这个边界等待“第三个”线程。

    下面的程序使用一个包含2000 000个字符串的集合,使用多个任务遍历该集合,并统计以a,b,c等开头的字符串的个数。

    class Program
        {
            public static IEnumerable<string> FillData(int size) 
            {
                var data = new List<string>(size);
                var r = new Random();
                for(int i = 0; i < size; i++)
                {
                    data.Add(GetString(r));
                }
                return data;
            }
    
            private static string GetString(Random r)
            {
                var sb = new StringBuilder(6);
                for(int i = 0; i < 6; i++)
                {
                    sb.Append((char)(r.Next(26) + 97));//随机挑选字母
                }
                return sb.ToString();
            }
    
            static int[] CalculationInTask(int jobNumber,int partitionSize,Barrier barrier,IList<string> coll)
            {
                List<string> data = new List<string>(coll);
                int start = jobNumber * partitionSize;
                int end = start + partitionSize;
                Console.WriteLine($"Task {Task.CurrentId}:partition from {start} to {end}");
                int[] charCount = new int[26];
                for(int j = start; j < end; j++)
                {
                    char c = data[j][0];
                    charCount[c - 97]++;
                }
                Console.WriteLine($"Calculation completed from task {Task.CurrentId},{charCount[0]} times a,{charCount[25]} times z");
                barrier.RemoveParticipant();
                Console.WriteLine($"Task {Task.CurrentId} removed from barrier, remaining" +
                    $"participantts {barrier.ParticipantsRemaining}");
                return charCount;
            }
    
            public static void Main(string[] args)
            {
                const int numberTasks = 2;
                const int partitionSize = 1000000;
                var data = new List<string>(FillData(partitionSize * numberTasks));
                var barrier = new Barrier(numberTasks + 1);
                var tasks = new Task<int[]>[numberTasks];
                for (int i = 0; i < partitionSize; i++)
                {
                    int jobNumber = i;
                    tasks[i] = Task.Run(() => CalculationInTask(jobNumber, partitionSize, barrier, data));
                    barrier.SignalAndWait();
                }
                var  resultCollection = tasks[0].Result.Zip(tasks[1].Result,(c1,c2)=>{
                    return c1 + c2;
                    });
                char ch = 'a';
                int sum = 0;
                foreach(var x in resultCollection)
                {
                    Console.WriteLine($"{ch++},count:{x}");
                    sum += x;
                }
                Console.WriteLine($"main finished {sum}");
                Console.WriteLine($"remaining {barrier.ParticipantsRemaining}");
            }
        }
    

    output

    Task 1:partition from 0 to 1000000
    Calculation completed from task 1,38533 times a,38281 times z
    Task 1 removed from barrier, remainingparticipantts 1
    

    使用ReaderWriterLockSlim

    ReaderWriterLockSlim代表了一个管理资源访问的锁,允许多个线程同时读取,以及独占用。

    class Program
        {
            static ReaderWriterLockSlim _rw = new ReaderWriterLockSlim();
            static Dictionary<int, int> _items = new Dictionary<int, int>();
            static void Read()
            {
                Console.WriteLine("Reading conttens of a dictionary");
                while (true)
                {
                    try
                    {
                        _rw.EnterReadLock();
                        foreach(var key in _items.Keys)
                        {
                            Thread.Sleep(TimeSpan.FromSeconds(0.1));
                            Console.WriteLine($"{Thread.CurrentThread.Name} reads {key}");
                        }
    
                    }
                    finally//注意:如果没有catch语句块,那么finally块就是必须的。  如果你不希望在这里处理异常,而当异常发生时提交到上层处理,但在这个地方无论发生异常,都要必须要执行一些操作,就可以使用try finally, 很典型的应用就是进行数据库操作
                    {
                        _rw.ExitReadLock();
                    }
                }
            }
            static void Write(string threadName)
            {
                while (true)
                {
                    try
                    {
                        int newKey = new Random().Next(250);
                        _rw.EnterUpgradeableReadLock();//先获取读锁后,读取数据,如果发现必须修改底层集合,只需要使用`EnterWriteLock`方法升级锁,然后快速执行一次写操作。
                        if (!_items.ContainsKey(newKey))
                        {
                            try
                            {
                                _rw.EnterWriteLock();
                                _items[newKey] = 1;
                                Console.WriteLine($"New key {newKey} is added to a dictionary by a {threadName}");
                            }
                            finally
                            {
                                _rw.ExitWriteLock();
                            }
                        }
                        Thread.Sleep(TimeSpan.FromSeconds(0.1));
                    }
                    finally
                    {
                        _rw.ExitUpgradeableReadLock();
                    }
                }
            }
            public static void Main()
            {
                new Thread(Read) { IsBackground = true,Name="ThreadOne" }.Start();
                new Thread(Read) { IsBackground = true,Name="ThreadTwo" }.Start();
                new Thread(Read) { IsBackground = true ,Name="ThreadThree"}.Start();
                new Thread(() => Write("Thread 1")) { IsBackground = true }.Start();
                new Thread(() => Write("Thread 2")) { IsBackground = true }.Start();
                Thread.Sleep(TimeSpan.FromSeconds(30));
            }
        }
    

    output:

    Reading conttens of a dictionary
    New key 211 is added to a dictionary by a Thread 1
    Reading conttens of a dictionary
    Reading conttens of a dictionary
    ThreadOne reads 211
    ThreadTwo reads 211
    ThreadThree reads 211
    ThreadThree reads 211
    ThreadTwo reads 211
    ThreadOne reads 211
    New key 239 is added to a dictionary by a Thread 2
    ThreadOne reads 211
    ThreadThree reads 211
    ThreadTwo reads 211
    ThreadTwo reads 239
    ThreadOne reads 239
    ThreadThree reads 239
    New key 225 is added to a dictionary by a Thread 1
    ThreadTwo reads 211
    ThreadOne reads 211
    ThreadThree reads 211
    ThreadOne reads 239
    ThreadTwo reads 239
    ThreadThree reads 239
    ThreadThree reads 225
    ThreadOne reads 225
    ThreadTwo reads 225
    New key 123 is added to a dictionary by a Thread 2
    ThreadTwo reads 211
    ThreadThree reads 211
    ThreadOne reads 211
    ThreadThree reads 239
    ThreadTwo reads 239
    ThreadOne reads 239
    ThreadThree reads 225
    ThreadOne reads 225
    ThreadTwo reads 225
    ThreadThree reads 123
    ThreadOne reads 123
    ThreadTwo reads 123
    New key 20 is added to a dictionary by a Thread 1
    ThreadOne reads 211
    ThreadTwo reads 211
    ThreadThree reads 211
    ThreadOne reads 239
    ThreadTwo reads 239
    ThreadThree reads 239
    ThreadTwo reads 225
    ThreadOne reads 225
    ThreadThree reads 225
    ThreadOne reads 123
    ThreadThree reads 123
    ThreadTwo reads 123
    ThreadOne reads 20
    ThreadThree reads 20
    ThreadTwo reads 20
    New key 106 is added to a dictionary by a Thread 1
    ThreadTwo reads 211
    ThreadOne reads 211
    ThreadThree reads 211
    ThreadTwo reads 239
    ThreadOne reads 239
    ThreadThree reads 239
    ThreadOne reads 225
    ThreadTwo reads 225
    ThreadThree reads 225
    ThreadTwo reads 123
    ThreadOne reads 123
    ThreadThree reads 123
    ThreadTwo reads 20
    ThreadOne reads 20
    ThreadThree reads 20
    ThreadOne reads 106
    ThreadTwo reads 106
    ThreadThree reads 106
    New key 75 is added to a dictionary by a Thread 1
    ThreadOne reads 211
    ThreadTwo reads 211
    ThreadThree reads 211
    ThreadTwo reads 239
    ThreadOne reads 239
    ThreadThree reads 239
    ThreadTwo reads 225
    ThreadOne reads 225
    ThreadThree reads 225
    ThreadOne reads 123
    ThreadTwo reads 123
    ThreadThree reads 123
    ThreadTwo reads 20
    ThreadOne reads 20
    ThreadThree reads 20
    ThreadOne reads 106
    ThreadTwo reads 106
    ThreadThree reads 106
    ThreadOne reads 75
    ThreadTwo reads 75
    ThreadThree reads 75
    New key 181 is added to a dictionary by a Thread 2
    ThreadTwo reads 211
    ThreadOne reads 211
    ThreadThree reads 211
    ThreadTwo reads 239
    ThreadOne reads 239
    ThreadThree reads 239
    ThreadOne reads 225
    ThreadTwo reads 225
    ThreadThree reads 225
    ThreadOne reads 123
    ThreadTwo reads 123
    ThreadThree reads 123
    ThreadTwo reads 20
    ThreadOne reads 20
    ThreadThree reads 20
    ThreadTwo reads 106
    ThreadOne reads 106
    ThreadThree reads 106
    ThreadOne reads 75
    ThreadTwo reads 75
    ThreadThree reads 75
    ThreadOne reads 181
    ThreadTwo reads 181
    ThreadThree reads 181
    New key 57 is added to a dictionary by a Thread 1
    ThreadTwo reads 211
    ThreadThree reads 211
    ThreadOne reads 211
    ThreadOne reads 239
    ThreadThree reads 239
    ThreadTwo reads 239
    ThreadTwo reads 225
    ThreadOne reads 225
    ThreadThree reads 225
    ThreadOne reads 123
    ThreadThree reads 123
    ThreadTwo reads 123
    ThreadThree reads 20
    ThreadOne reads 20
    ThreadTwo reads 20
    ThreadOne reads 106
    ThreadThree reads 106
    ThreadTwo reads 106
    ThreadOne reads 75
    ThreadThree reads 75
    ThreadTwo reads 75
    ThreadOne reads 181
    ThreadThree reads 181
    ThreadTwo reads 181
    ThreadThree reads 57
    ThreadOne reads 57
    ThreadTwo reads 57
    New key 143 is added to a dictionary by a Thread 2
    ThreadThree reads 211
    ThreadTwo reads 211
    ThreadOne reads 211
    ThreadTwo reads 239
    ThreadOne reads 239
    ThreadThree reads 239
    ThreadOne reads 225
    ThreadTwo reads 225
    ThreadThree reads 225
    ThreadOne reads 123
    ThreadTwo reads 123
    ThreadThree reads 123
    ThreadOne reads 20
    ThreadTwo reads 20
    ThreadThree reads 20
    ThreadOne reads 106
    ThreadTwo reads 106
    ThreadThree reads 106
    ThreadOne reads 75
    ThreadThree reads 75
    ThreadTwo reads 75
    ThreadOne reads 181
    ThreadTwo reads 181
    ThreadThree reads 181
    ThreadOne reads 57
    ThreadThree reads 57
    ThreadTwo reads 57
    ThreadOne reads 143
    ThreadThree reads 143
    ThreadTwo reads 143
    New key 23 is added to a dictionary by a Thread 1
    ThreadThree reads 211
    ThreadOne reads 211
    ThreadTwo reads 211
    ThreadThree reads 239
    ThreadOne reads 239
    ThreadTwo reads 239
    ThreadThree reads 225
    ThreadOne reads 225
    ThreadTwo reads 225
    ThreadThree reads 123
    ThreadOne reads 123
    ThreadTwo reads 123
    ThreadThree reads 20
    ThreadTwo reads 20
    ThreadOne reads 20
    ThreadThree reads 106
    ThreadTwo reads 106
    ThreadOne reads 106
    ThreadThree reads 75
    ThreadTwo reads 75
    ThreadOne reads 75
    ThreadThree reads 181
    ThreadTwo reads 181
    ThreadOne reads 181
    ThreadThree reads 57
    ThreadOne reads 57
    ThreadTwo reads 57
    ThreadThree reads 143
    ThreadTwo reads 143
    ThreadOne reads 143
    ThreadThree reads 23
    ThreadOne reads 23
    ThreadTwo reads 23
    New key 6 is added to a dictionary by a Thread 2
    ThreadTwo reads 211
    ThreadOne reads 211
    ThreadThree reads 211
    ThreadTwo reads 239
    ThreadThree reads 239
    ThreadOne reads 239
    ThreadTwo reads 225
    ThreadThree reads 225
    ThreadOne reads 225
    ThreadThree reads 123
    ThreadTwo reads 123
    ThreadOne reads 123
    ThreadThree reads 20
    ThreadTwo reads 20
    ThreadOne reads 20
    ThreadThree reads 106
    ThreadTwo reads 106
    ThreadOne reads 106
    ThreadThree reads 75
    ThreadTwo reads 75
    ThreadOne reads 75
    ThreadTwo reads 181
    ThreadThree reads 181
    ThreadOne reads 181
    ThreadThree reads 57
    ThreadTwo reads 57
    ThreadOne reads 57
    ThreadThree reads 143
    ThreadTwo reads 143
    ThreadOne reads 143
    ThreadTwo reads 23
    ThreadThree reads 23
    ThreadOne reads 23
    ThreadThree reads 6
    ThreadTwo reads 6
    ThreadOne reads 6
    New key 4 is added to a dictionary by a Thread 2
    ThreadTwo reads 211
    ThreadOne reads 211
    ThreadThree reads 211
    ThreadTwo reads 239
    ThreadOne reads 239
    ThreadThree reads 239
    ThreadOne reads 225
    ThreadThree reads 225
    ThreadTwo reads 225
    ThreadThree reads 123
    ThreadOne reads 123
    ThreadTwo reads 123
    ThreadThree reads 20
    ThreadOne reads 20
    ThreadTwo reads 20
    ThreadOne reads 106
    ThreadThree reads 106
    ThreadTwo reads 106
    ThreadOne reads 75
    ThreadThree reads 75
    ThreadTwo reads 75
    ThreadThree reads 181
    ThreadOne reads 181
    ThreadTwo reads 181
    ThreadThree reads 57
    ThreadOne reads 57
    ThreadTwo reads 57
    ThreadThree reads 143
    ThreadOne reads 143
    ThreadTwo reads 143
    ThreadOne reads 23
    ThreadThree reads 23
    ThreadTwo reads 23
    ThreadThree reads 6
    ThreadOne reads 6
    ThreadTwo reads 6
    ThreadThree reads 4
    ThreadOne reads 4
    ThreadTwo reads 4
    New key 18 is added to a dictionary by a Thread 2
    ThreadTwo reads 211
    ThreadOne reads 211
    ThreadThree reads 211
    ThreadTwo reads 239
    ThreadOne reads 239
    ThreadThree reads 239
    ThreadTwo reads 225
    ThreadOne reads 225
    ThreadThree reads 225
    ThreadOne reads 123
    ThreadTwo reads 123
    ThreadThree reads 123
    ThreadTwo reads 20
    ThreadOne reads 20
    ThreadThree reads 20
    ThreadOne reads 106
    ThreadTwo reads 106
    ThreadThree reads 106
    ThreadOne reads 75
    ThreadTwo reads 75
    ThreadThree reads 75
    ThreadOne reads 181
    ThreadTwo reads 181
    ThreadThree reads 181
    ThreadTwo reads 57
    ThreadOne reads 57
    ThreadThree reads 57
    ThreadTwo reads 143
    ThreadOne reads 143
    ThreadThree reads 143
    ThreadTwo reads 23
    ThreadOne reads 23
    ThreadThree reads 23
    ThreadTwo reads 6
    ThreadOne reads 6
    ThreadThree reads 6
    ThreadOne reads 4
    ThreadTwo reads 4
    ThreadThree reads 4
    ThreadOne reads 18
    ThreadThree reads 18
    ThreadTwo reads 18
    New key 31 is added to a dictionary by a Thread 1
    ThreadTwo reads 211
    ThreadOne reads 211
    ThreadThree reads 211
    ThreadTwo reads 239
    ThreadThree reads 239
    ThreadOne reads 239
    ThreadOne reads 225
    ThreadTwo reads 225
    ThreadThree reads 225
    ThreadTwo reads 123
    ThreadOne reads 123
    ThreadThree reads 123
    ThreadTwo reads 20
    ThreadOne reads 20
    ThreadThree reads 20
    ThreadOne reads 106
    ThreadTwo reads 106
    ThreadThree reads 106
    ThreadTwo reads 75
    ThreadOne reads 75
    ThreadThree reads 75
    ThreadOne reads 181
    ThreadTwo reads 181
    ThreadThree reads 181
    ThreadTwo reads 57
    ThreadOne reads 57
    ThreadThree reads 57
    ThreadOne reads 143
    ThreadTwo reads 143
    ThreadThree reads 143
    ThreadOne reads 23
    ThreadThree reads 23
    ThreadTwo reads 23
    ThreadOne reads 6
    ThreadThree reads 6
    ThreadTwo reads 6
    ThreadOne reads 4
    ThreadTwo reads 4
    ThreadThree reads 4
    ThreadOne reads 18
    ThreadTwo reads 18
    ThreadThree reads 18
    ThreadOne reads 31
    ThreadThree reads 31
    ThreadTwo reads 31
    New key 176 is added to a dictionary by a Thread 2
    .....省略...
    

    有两种锁,三种状态,读锁允许多线程读取数据,写锁,会阻止阅读者读取数据,从而浪费大量时间,因此获取写锁后,集合会处于阻塞状态。为了最小化阻塞时间,可以使用EnterUpgradeableReadLockExitUpgradeableReadLock方法。在上述代码中,先获取读锁后,读取数据,如果发现必须修改底层集合,只需要使用EnterWriteLock方法升级锁,然后快速执行一次写操作。

    总结

    对于以上的各种锁,从其最根本上的功能来讲,都是一样的,那就是协调各个线程,有序工作。从方法论的角度,如果一个事物如果要对其他事物起协调作用,它必须处于一个全局的地位,并且深入在其他事物之中。反观以上各种锁,也确实是这样来起到协调各个线程的作用的,首先在全局定义锁,然后将该锁插入线程执行的代码中
    那么,下一个问题,自然是”如何实现协调功能?”,所谓协调功能就是管控线程什么时候运行,什么时候暂停等待的功能,其实我更喜欢把它抽象为“门”,从这个层次来讲,协调功能就是“什么时候开门,什么时候关门”的功能,对于这个问题,目前学到的有基于互斥锁的Mutex,Semaphore,基于事件的AutoResetEvent,ManualResetEventSlim,基于计数事件的CountDownEvent,与计数类似的Barrier,基于互斥锁的是依靠互斥锁本身来实现“门”的开关,基于事件的,就允许我们通过手动对“门”进行开关,而计数的就是每个线程执行完任务,发出一次信号,然后信号数量达到既定的数字才得以“开门”。

  • 相关阅读:
    org.apache.commons.net.ftp
    java中的匿名内部类总结
    有关JVM处理Java数组方法的思考
    使用myeclipse创建带注解的model实体类
    Maven导出Project依赖的jar包
    annotation-config, annotation-driven, compont-scan 区别
    hibernate annotation注解方式来处理映射关系
    关于Spring事务<tx:annotation-driven/>的理解(Controller可以使用@Transactional)
    Hibernate批量操作(二)
    Hibernate批量操作(一)
  • 原文地址:https://www.cnblogs.com/johnyang/p/15877042.html
Copyright © 2020-2023  润新知