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一般配合使用实现同步。

  • 相关阅读:
    使用CustomValidate自定义验证控件
    C#中金额的大小写转换
    Andriod出错之Unable to build: the file dx.jar was not loaded from the SDK folder!
    VC 编写的打字练习
    机房工作笔记Ping只有单向通
    web服务协同学习笔记(1)
    Dll 学习3 将MDI子窗口封装在DLL中
    机房工作学习文件共享
    Andriod出错之Failed to find an AVD compatible with target 'Android 2.2'
    Andriod出错之wrapper was not properly loaded first
  • 原文地址:https://www.cnblogs.com/enuo/p/2283370.html
Copyright © 2011-2022 走看看