多线程之共享资源

1.lock

   Lock锁定一段代码

   lock 确保当一个线程位于代码的临界区时,另一个线程不进入临界区。如果其他线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。

   通常,应避免锁定 public 类型,否则实例将超出代码的控制范围。常见的结构 lock (this)、lock (typeof (MyType)) 和 lock ("myLock") 

   违反此准则:

  • 如果实例可以被公共访问,将出现 lock (this) 问题。
  • 如果 MyType 可以被公共访问,将出现 lock(typeof (MyType)) 问题。

   由于进程中使用同一字符串的任何其他代码将共享同一个锁,所以出现 lock(“myLock”) 问题。最佳做法是定义 private 对象来锁定, private static 对象变量来保护所有实例所共有的数据。

2.Monitor 

  Monitor用来锁定一个对象

  Monitor具有以下的功能:

  • 与某个对象关联。
  • 它是未绑定的,可以直接从上下文中引用。
  • 不能创建Monitor实例

 将为每个同步对象维护以下信息:

  • 对当前持有锁的线程的引用。
  • 对就绪队列的引用,它包含准备获取锁的线程。
  •  对等待队列的引用,它包换正在等待锁定对象状态变化通知的线程。

Code:   

 1 class MonitorSample
 2     {
 3         const int MAX_LOOP_TIME = 1000;
 4         Queue mQueue;
 5 
 6         public MonitorSample()
 7         {
 8             mQueue = new Queue();
 9         }
10         public void FirstThread()
11         {
12             int counter = 0;
13             lock (mQueue)
14             {
15                 while (counter < MAX_LOOP_TIME)
16                 {
17                     Monitor.Wait(mQueue);
18                     mQueue.Enqueue(counter);
19                     Console.WriteLine("Write:{0}", counter);
20                     Monitor.Pulse(mQueue);
21                     counter++;
22                 }
23             }
24         }
25         public void SecondThread()
26         {
27             lock (mQueue)
28             {
29                 Monitor.Pulse(mQueue);
30                 while (Monitor.Wait(mQueue, 1000))
31                 {
32                     int counter = (int)mQueue.Dequeue();
33                     Console.WriteLine("Read:{0}",counter);
34                     Monitor.Pulse(mQueue);
35                 }
36             }
37         }
38         public int GetQueueCount()
39         {
40             return mQueue.Count;
41         }
42 
43         static void Main(string[] args)
44         {
45             MonitorSample test = new MonitorSample();
46             Thread tFirst = new Thread(new ThreadStart(test.FirstThread));
47             Thread tSecond = new Thread(new ThreadStart(test.SecondThread));
48             tFirst.Start();
49             tSecond.Start();
50             tFirst.Join();
51             tSecond.Join();
52             Console.WriteLine("Queue Count = " + test.GetQueueCount().ToString());
53             Console.ReadKey();
54         }
55     }
View Code

   上面的例子FirstThread方法首先写入一个值,SecondThead方法处于等待状态,直到收到Monitor.Pulse信号读出值,FirstThread再等待Monitor.Pulse信号写入值。

3.Mutex.

  当两个或更多线程需要同时访问一个共享资源时,系统需要使用同步机制来确保一次只有一个线程使用该资源。 Mutex 是同步基元,它只向一个线程授予对共享资源的独占访问权。 如果一个线程获取了互斥体,则要获取该互斥体的第二个线程将被挂起,直到第一个线程释放该互斥体。

  可以使用 WaitHandle.WaitOne 方法请求互斥体的所属权。 拥有互斥体的线程可以在对 WaitOne 的重复调用中请求相同的互斥体而不会阻止其执行。 但线程必须调用 ReleaseMutex 方法同样多的次数以释放互斥体的所属权。 Mutex 类强制线程标识,因此互斥体只能由获得它的线程释放。 相反,Semaphore 类不强制线程标识。

   Code:

 1 class Test
 2 {
 3     private static Mutex mut = new Mutex();
 4     private const int numIterations = 1;
 5     private const int numThreads = 3;
 6 
 7     static void Main()
 8     {
 9         for (int i = 0; i < numThreads; i++)
10         {
11             Thread myThread = new Thread(new ThreadStart(MyThreadProc));
12             myThread.Name = String.Format("Thread{0}", i + 1);
13             myThread.Start();
14             Console.ReadKey();
15         }
16     }
17 
18     private static void MyThreadProc()
19     {
20         for (int i = 0; i < numIterations; i++)
21         {
22             UseResource();
23         }
24     }
25     private static void UseResource()
26     {
27         mut.WaitOne();
28         Console.WriteLine("{0} has entered the protected area",
29             Thread.CurrentThread.Name);
30         Thread.Sleep(500);
31         Console.WriteLine("{0} is leaving the protected area
",
32             Thread.CurrentThread.Name);
33         mut.ReleaseMutex();
34     }
35 }
View Code

4.Semaphore 

  限制可同时访问某一资源或资源池的线程数。

  使用 Semaphore 类可控制对资源池的访问。 线程通过调用 WaitOne 方法(从 WaitHandle 类继承)进入信号量,并通过调用 Release 方法释放信号量。

  信号量的计数在每次线程进入信号量时减小,在线程释放信号量时增加。 当计数为零时,后面的请求将被阻塞,直到有其他线程释放信号量。 当所有的线程都已释放信号量时,计数达到创建信号量时所指定的最大值。

被阻止的线程并不一定按特定的顺序(如 FIFO 或 LIFO)进入信号量。

  信号量分为两种类型:局部信号量和已命名的系统信号量。 如果您使用接受名称的构造函数创建 Semaphore 对象,则该对象与具有该名称的操作系统信号量关联。 已命名的系统信号量在整个操作系统中都可见,可用于同步进程活动。 您可以创建多个 Semaphore 对象来表示同一个已命名的系统信号量,也可以使用 OpenExisting 方法打开现有的已命名系统信号量。

  下面的代码示例创建一个最大计数为 3、初始计数为 0 的全局信号量。 此示例启动五个线程,这些线程都将阻止等待该信号量。 主线程使用 Release(Int32) 方法重载,以便将信号量计数增加为其最大值,从而允许三个线程进入该信号量。 每个线程都使用 Thread.Sleep 方法等待一秒钟以便模拟工作,然后调用 Release() 方法重载以释放信号量。 每次释放信号量时,都显示前一个信号量计数。 控制台消息对信号量的使用进行跟踪。 每个线程的模拟工作间隔都稍有增加,以使输出更为易读。

 

 1 public class Example
 2 {
 3     public static Semaphore _pool;
 4     private static int _padding;
 5 
 6     public static void Main()
 7     {
 8         ExampleTest my = new ExampleTest(); //实例化。声明全局信号量mySemaphore
 9         _pool = Semaphore.OpenExisting("mySemaphore");
10         for (int i = 1; i <= 5; i++)
11         {
12             Thread t = new Thread(new ParameterizedThreadStart(Worker));
13             t.Start(i);
14         }
15         Thread.Sleep(500);
16         Console.WriteLine("Main thread calls Release(3).");
17         _pool.Release(3);
18         Console.WriteLine("Main thread exits.");
19         Console.ReadKey();
20     }
21 
22     private static void Worker(object num)
23     {
24         Console.WriteLine("Thread {0} begins " + "and waits for the semaphore.", num);
25         _pool.WaitOne();
26         int padding = Interlocked.Add(ref _padding, 100);
27         Console.WriteLine("Thread {0} enters the semaphore.", num);
28         Thread.Sleep(1000 + padding);
29         Console.WriteLine("Thread {0} releases the semaphore.", num);
30         Console.WriteLine("Thread {0} previous semaphore count: {1}", num, _pool.Release());
31     }
32 }
33 
34 public class ExampleTest
35 {
36     private Semaphore mySemaphore = new Semaphore(0, 3, "mySemaphore");
37 }
View Code

 5.Interlocked

   为多个线程共享的变量提供原子操作

 1  class MyInterlockedExchangeExampleClass
 2     {
 3         private static int usingResource = 0;
 4         private const int numThreadIterations = 5;
 5         private const int numThreads = 10;
 6         static void Main()
 7         {
 8             Thread myThread;
 9             Random rnd = new Random();
10             for (int i = 0; i < numThreads; i++)
11             {
12                 myThread = new Thread(new ThreadStart(MyThreadProc));
13                 myThread.Name = String.Format("Thread{0}", i + 1);
14                 Thread.Sleep(rnd.Next(0, 1000));
15                 myThread.Start();
16             }
17             Console.ReadKey();
18         }
19         private static void MyThreadProc()
20         {
21             for (int i = 0; i < numThreadIterations; i++)
22             {
23                 UseResource();
24                 Thread.Sleep(1000);
25             }
26         }
27         static bool UseResource()
28         {
29             if (0 == Interlocked.Exchange(ref usingResource, 1))
30             {
31                 Console.WriteLine("{0} acquired the lock", Thread.CurrentThread.Name);
32                 Thread.Sleep(500);
33                 Console.WriteLine("{0} exiting lock", Thread.CurrentThread.Name);
34                 Interlocked.Exchange(ref usingResource, 0);
35                 return true;
36             }
37             else
38             {
39                 Console.WriteLine("   {0} was denied the lock", Thread.CurrentThread.Name);
40                 return false;
41             }
42         }
43 
44     }
View Code

5.volatile

  volatile关键字指示一个字段可以由多个同时执行的线程修改。

  volatile变量可以实现线程安全,但其应用有限,只有其状态完全独立于其他状态时才可使用。

原文地址:https://www.cnblogs.com/mandel/p/3503269.html