zoukankan      html  css  js  c++  java
  • C#线程从陌生到熟悉(5)

    互斥体Mutex
    Mutex是同步基元,他只向一个线程授予队共享资源的独占访问权.如果线程获取了互斥体,则要获取该互斥体的第二个线程将被挂起,直到第一个线程释放互斥体.
    首先来看Mutex类的定义.

    1 public sealed class Mutex : WaitHandle
    2 {
    3 public static Mutex OpenExisting(string name);
    4 public void ReleaseMutex();
    5 public MutexSecurity GetAccessControl();
    6 public void SetAccessControl(MutexSecurity mutexSecurity);
    7 ...
    8 }

    我们主要看看这四个方法,OpenExisting打开互斥体,注意是一个静态方法.ReleaseMutex释放互斥体.MutexSecurity表是对互斥体的访问权限. SetAccessControl设置已命名的系统互斥体的访问控制安全性.
    这个类声明为sealed ,表示不能继承.有一个比较重要的是,他是从WaitHandle继承而来,WaitHandle这个类应该比较熟悉了,前面已经写过了.主要是这个类的Wait的方法.下面我们来看个简单的例子:

     1 using System;
    2 using System.Collections.Generic;
    3 using System.Linq;
    4 using System.Text;
    5 using System.Threading;
    6 using System.Security.AccessControl;
    7
    8 namespace ConsoleApplication11
    9 {
    10 class Program
    11 {
    12 Mutex m = new Mutex(false, "myMutex");
    13 int val = 100;
    14 static void Main(string[] args)
    15 {
    16 Program p = new Program();
    17 Thread t1 = new Thread(p.Write);
    18 Thread t2 = new Thread(p.Read);
    19 t1.Start();
    20 t2.Start();
    21 Thread.Sleep(3000);
    22 Mutex m1 = Mutex.OpenExisting("myMutex");
    23 m1.WaitOne();
    24 Console.WriteLine("结束!");
    25 m1.ReleaseMutex();
    26
    27 }
    28 public void Write()
    29 {
    30
    31 m.WaitOne();
    32 Thread.Sleep(2000);
    33 try
    34 {
    35 val += 1;
    36 Console.WriteLine("写val的值为{0}", val);
    37
    38 }
    39 finally
    40 {
    41 m.ReleaseMutex();
    42 }
    43 }
    44 public void Read()
    45 {
    46 m.WaitOne();
    47 Thread.Sleep(1000);
    48 try
    49 {
    50 Console.WriteLine("读val的值为{0}", val);
    51 }
    52 finally
    53 {
    54 m.ReleaseMutex();
    55 }
    56 }
    57
    58
    59 }
    60 }

    运行结果为

    Write方法首先获得Mutex对象,即时运行Thread.Sleep(2000),但是Read方法需要Mutex对象,现在没有释放,所以首先运行Write方法,然后运行Read这个方法.最后结束.

    Interlocked

    Interlocked这个类为多个类共享的变量提供原子操作.首先看看他的定义:

    1 public static class Interlocked
    2 {
    3 public static int CompareExchange(ref int location1, int value, int comparand);
    4 public static int Decrement(ref int location);
    5 public static int Exchange(ref int location1, int value);
    6 public static int Increment(ref int location);
    7 ......
    8 }

    Exchange函数自动交换指定变量的值.CompareExchange比较两个值以及根据比较的结果将第三个存储在一个变量中.Increment和Decrement函数将递增和递减变量与检查结果值得操作组合起来.请看下面的例子:

     1 using System;
    2 using System.Collections.Generic;
    3 using System.Linq;
    4 using System.Text;
    5 using System.Threading;
    6
    7 namespace ConsoleApplication12
    8 {
    9 class Program
    10 {
    11 static int AsyncOps = 6;
    12 static AutoResetEvent asyncOpsAreDone = new AutoResetEvent(false);
    13 static Book book=new Book();
    14 static void Main(string[] args)
    15 {
    16 for (int i = 0; i < 6; i++)
    17 {
    18 ThreadPool.QueueUserWorkItem(new WaitCallback(ChangeBook), i);
    19
    20 }
    21 asyncOpsAreDone.WaitOne();
    22 Console.WriteLine("操作结束!");
    23 }
    24 static void ChangeBook(Object state)
    25 {
    26 int threadnum = (Int32)state;
    27 if (threadnum % 2 != 0)
    28 {
    29 book.Read(threadnum);
    30 }
    31 else
    32 {
    33 book.Write(threadnum);
    34 }
    35 if (Interlocked.Decrement(ref AsyncOps) == 0)
    36 {
    37 asyncOpsAreDone.Set();
    38 }
    39 }
    40 }
    41
    42
    43 public class Book
    44 {
    45 public void Write(int i)
    46 {
    47 Console.WriteLine("线程{0}读书",i);
    48 }
    49 public void Read(int i)
    50 {
    51 Console.WriteLine("线程{0}写书",i);
    52 }
    53 }
    54 }

    结果为

    结果可能是变化的.线程运行顺序不一定相同.AutoRestEvent前面已经写过他的介绍了。首先启动6个字线程,每次操作完(原子操作),调用Decrement方法是asyncOpts减少1。asyncOpts为0后,将通知主线程继续运行。如果将asyncOpts初始值写小点,就可以看到“操作结束”的输出会在线程输出之中。

    几种互斥技术的比较:
    1.Monitor.Enter和Monitor.Exit和lock(obj)都是基于对象锁技术.这里注意一点,如何去所定一个类(静态).如果尝试同时运用Monitor和lock,而且他们锁定的对象不一样时.结果会怎么样,这个我想大家都应该知道的.既然都是基于对象锁技术,锁是和临界资源捆绑在一起.用到这些的时候,一个要和一个对象关联的.这种方式粒度较粗。
    2.Mutex是基于自身的锁.通过将一个临界资源更一个Mutex实力关联,要求所有请求该临界资源的线程首先获得更他相关的Mutex锁.这种方式的锁定力度可以自由控制,可以是一个对象,-段代码甚至整个进程。注意下,Mutex是从WaitHandle类派生而来,Mutex很多都是用WaitHandle的方法。
    3.Interlocked提供粒度最细的锁,它不依赖于锁定,而基于原子操作的不可分割性,他使增,减,交换,比较等动作成为一个不可分割的原子操作实现互斥。

    同步

    同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源。

     .NET公共运行库实现同步的基本方法有三种。 前面很多地方已经用到同步的知识,这里我简单介绍下。 Thred.Join()这个方法阻塞调用线程,直到某个线程终止时为止。 Thread thread=new Thread(ThreadFun); thread.start(); thread.Join(); WaitHandle Waithandle类封装Win32同步句柄,并用于表示运行库中所有允许执行等待操作的同步对象。从WaitHandle派生的类包括Mutex,AutoResetEvent,ManualResetEvent。前面两个类已经被多次用到!这里不在介绍了。

     ManualResetEvent是可以手动设置为非终止状态。手动重置的事件的状态始终保持终止,直到Reset方法将其设置为非终止状态。下面看下AutoResetEvent和ManualResetEvent的用法区别,

    1 AutoResetEvent autoasyncOpIsDone = new AutoResetEvent (false); ThreadPool.QueueUserWorkItem( new WaitCallback(myFunction),autoasyncOpIsDone);
    2 autoasyncOpIsDone.WaitOne(); //asyncOpIsDone自动被复位,这是AutoResetEvent 的用法
    3
    4 ManualResetEvent manualasyncOpIsDone =new ManualResetEvent(false); ThreadPool.QueueUserWorkItem( new WaitCallback(myFunction),manualasyncOpIsDone);
    5 manualasyncOpIsDone.WaitOne(); manualasyncOpIsDone.Reset();//注意这句,手工复位。这是ManualResetEvent的用法。

    Monitor和lock语句配合实现同步,这个给个例子:

      1 using System;
    2 using System.Collections.Generic;
    3 using System.Linq;
    4 using System.Text;
    5 using System.Threading;
    6
    7 namespace ConsoleApplication13
    8 {
    9 class Program
    10 {
    11 static void Main(string[] args)
    12 {
    13 DemoBuffer();
    14 }
    15
    16 public static void BufferRead()
    17 {
    18 Object o = AppDomain.CurrentDomain.GetData("Buffer");
    19 if (o != null)
    20 {
    21 Buffer buffer = (Buffer)o;
    22 for (int i = 0; i < 8; i++)
    23 {
    24 Console.WriteLine("读线程\t{0}读到字符{1}",
    25 Thread.CurrentThread.GetHashCode(), buffer.Get());
    26 }
    27
    28 }
    29 Console.WriteLine("读取结束");
    30 }
    31
    32 public static void BufferWrite()
    33 {
    34 object o = AppDomain.CurrentDomain.GetData("Buffer");
    35 char[] msg = { 'E', 'N', 'U', 'O', 'e', 'n', 'u', 'o','t','v' };
    36 if (o != null)
    37 {
    38 Buffer buffer = (Buffer)o;
    39 for (int i = 0; i < msg.GetLength(0); i++)
    40 {
    41
    42 Console.WriteLine("写线程\t{0}写字符{1}", Thread.CurrentThread.GetHashCode(), msg[i]);
    43 buffer.Put(msg[i]);
    44 }
    45 }
    46 Console.WriteLine("写入结束");
    47 }
    48
    49 public static void DemoBuffer()
    50 {
    51 Buffer buffer = new Buffer();
    52 AppDomain.CurrentDomain.SetData("Buffer", buffer);
    53 Thread threadReader = new Thread(BufferRead);
    54 Thread threadWrite = new Thread(BufferWrite);
    55 threadReader.Start();
    56 threadWrite.Start();
    57 threadReader.Join();
    58 threadWrite.Join();
    59 Console.Read();
    60 }
    61
    62 }
    63 public class Buffer
    64 {
    65 const int size = 4;
    66 char[] buffer = new char[size];
    67 int n = 0, head = 0, tail = 0;
    68 public void Put(char ch)
    69 {
    70 lock (this)
    71 {
    72 n++;
    73 while (n > size)
    74 {
    75 // n = size;
    76 Monitor.Wait(this);
    77 }
    78 buffer[tail] = ch;
    79 tail = (tail + 1)%size;
    80 //如果缓冲区为空,则通知所有等待线程
    81 if (n <= 0)
    82 {
    83 Monitor.PulseAll(this);
    84 Monitor.Wait(this);
    85 }
    86
    87 }
    88 }
    89 public char Get()
    90 {
    91 char ch;
    92 lock (this)
    93 {
    94 n--;
    95 //如果缓冲区为空,则等待线程写入数据
    96 while (n < 0)
    97 {
    98 // n = 0;
    99 Monitor.PulseAll(this);
    100 Monitor.Wait(this);
    101 }
    102 ch = buffer[head];
    103 head = (head+ 1)%size;
    104 //如果缓冲区满了,则通知所有的等待线程
    105 if (n >= size)
    106 {
    107 Monitor.PulseAll(this);
    108 }
    109 Monitor.PulseAll(this);
    110 return ch;
    111 }
    112 }
    113 }
    114
    115 }


    运行结果为

    这个例子的运行顺序还是相当复杂的.首先执行BufferRead这个方法,此后执行Buffer的Get()方法,作完n--后,n=-1;注意一点,此时对象已被Get()方法锁定,Push()方法在等代锁,lock内的部分暂时还没执行.n=-1<0执行Monitor.PulseAll(this)和Monitor.Wait(this);通知等待线程锁定对象状态的更改.释放对象锁,直到再次获得锁.这是Push()方法运行,n=0,临界资源有一笔数据.n=0<=0又执行了上面两个方法.此时Get()将再次获得对象锁,读出数据,再次调用Monitor.PulseAll(this),最后lock里面代码执行结束,释放了锁,put()方法获得锁然后走出了lock里面的语句。至此n=0;然后BufferRead方法又执行,和上面的结果就一样了。(BufferRead基本上限于BufferWrite运行,可能会有不同,我试了好多次)。整个执行的过程有点繁琐。

    下面对这三个做下总结:

     1.Join用于等待特定的线程结束,经常用于主线程和子线程的同步。比如说,主线程需要等待某个线程做完某件事才继续,这时就可以用Join这个方法。

     2.AutoResetEvent和ManualReset用事件信号的方式实现线程之间的同步。经常用的方法为WaitOne()和set();

     3.Monitor和lock一般配合使用实现同步。

  • 相关阅读:
    一线架构师实践指南第三编阅读笔记
    学习七
    《信息领域热词分析》,如何设计编码实现六种质量属性战术
    学习六
    一线架构师实践指南第二部分阅读笔记
    学习五
    echarts实现省市区地图下钻
    python提取文本关键词
    后缀自动机复习笔记
    bzoj 1552
  • 原文地址:https://www.cnblogs.com/enuo/p/2283370.html
Copyright © 2011-2022 走看看