查看: 778|回复: 0

[.NET源码] 多线程编程学习笔记——线程同步(一)

发表于 2017-11-15 08:00:02
句号论坛
接上文 多线程编程学习笔记-基础(一) 接上文 多线程编程学习笔记-基础(二) 接上文 多线程编程学习笔记-基础(三)

就如上一篇文章(多线程编程学习笔记-基础(三))中的示例代码十,一样如果多线程使用共享变量,就会涉及到一个线程同步的问题。那如何解决呢?

方法有三:

1) 重构程序,移除多线程的共享变量,让一个线程只访问一个自有变量

2) 使用原子操作,一个操作只占用一个量子时间,一次完成,只有当当前操作完成之后,其他线程才能进行操作。这样可以避免使用独占锁,避免死锁。

3) 通过NET构架提供的Mutex、AutoRestEvent、CountDownEven、SpinWait等类,来进行线程间的同步。


一、使用InterLocked类

在上一篇文章中,我们使用lock来解决多线程访问带来的问,而在这里我们使用InterLocked类提供的原子操作,从而帮助我们不需要使用lock锁来锁定,以免造成死锁。

接下来我们改造一下上一篇文章中的代码,代码如下。

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading; //引入线程
  6. using System.Diagnostics;
  7. namespace ThreadSynchronousDemo
  8. {
  9. class Program
  10. {
  11. static void Main(string[] args)
  12. {
  13. Console.WriteLine("开始,InterLocked 同步");
  14. var c = new Counter();
  15. Thread t = new Thread(() => Count(c));
  16. var t3 = new Thread(() => Count(c));
  17. var t2 = new Thread(() => Count(c));
  18. t.Name = "线程1";
  19. //启动线程
  20. t.Start();
  21. t2.Name = "线程2";
  22. t2.Start();
  23. t3.Name = "线程3";
  24. t3.Start();
  25. t.Join();
  26. t2.Join();
  27. t3.Join();
  28. Console.WriteLine(string.Format("没有加锁的多线程总计:{0}", c.Count));
  29. Console.WriteLine("----------------------------");
  30. var c1 = new CounterInterLocked();
  31. var t4 = new Thread(() => Count(c1));
  32. t4.Name = "线程4";
  33. var t5 = new Thread(() => Count(c1));
  34. t5.Name = "线程5";
  35. var t6 = new Thread(() => Count(c1));
  36. t6.Name = "线程6";
  37. t4.Start();
  38. t5.Start();
  39. t6.Start();
  40. t4.Join();
  41. t5.Join();
  42. t6.Join();
  43. Console.WriteLine(string.Format("InterLocked的多线程总计:{0}", c1.Count));
  44. Console.Read();
  45. }
  46. static void Count(CountBase cnt)
  47. {
  48. for (int i = 0; i < 100000; i++)
  49. {
  50. cnt.Incerement();
  51. cnt.Dncerement();
  52. }
  53. }
  54. }
  55. abstract class CountBase
  56. {
  57. public abstract void Incerement();
  58. public abstract void Dncerement();
  59. }
  60. class Counter : CountBase
  61. {
  62. public int Count { get; private set; }
  63. public override void Dncerement()
  64. {
  65. Count--;
  66. }
  67. public override void Incerement()
  68. {
  69. Count++;
  70. }
  71. }
  72. class CounterInterLocked : CountBase
  73. {
  74. private int m_count;
  75. public int Count { get { return m_count; } }
  76. public override void Dncerement()
  77. {
  78. Interlocked.Decrement(ref m_count);
  79. }
  80. public override void Incerement()
  81. {
  82. Interlocked.Increment(ref m_count);
  83. }
  84. }
  85. }
复制代码

此程序运行结果如下图,跟上一篇中的示例十,结果是一样的。只是代码上的区别。

二、使用Mutex类

1. 接下来我们来学习使用Mutex类来实现线程间的同步问题。

2. 在程序启动时,设置InitialOwner为false,这表示如果互斥量已经建立,则允许程序获取互斥量。如果没有互斥量,则程序直接运行,等待接收任意键,然后释放互斥量。

3.代码如下:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading; //引入线程
  6. using System.Diagnostics;
  7. namespace ThreadSynchronousDemo
  8. {
  9. class Program
  10. {
  11. const string mutexName = "syncMutex";
  12. static void Main(string[] args)
  13. {
  14. Console.WriteLine("开始,Mutex 同步");
  15. using (var mut=new Mutex(false,mutexName))
  16. {
  17. if (!mut.WaitOne(TimeSpan.FromSeconds(5),false))
  18. {
  19. Console.WriteLine("等待5秒之后运行。。。。");
  20. }
  21. else
  22. {
  23. Console.WriteLine("正在运行。。。。,请输入任意键");
  24. Console.ReadLine();
  25. mut.ReleaseMutex();
  26. Console.WriteLine("释放互斥量");
  27. }
  28. }
  29. Console.Read();
  30. }
  31. }
  32. }
复制代码

4.运行结果如下图。

在debug目录下,先运行主程序,如上图中1,则程序1正常运行,此时如果再次打开Debug目录下的应用主程序,则运行结果如上图中2。说明互斥量起作用了。

5.先在上图主程序1中输入k,然后回车,结果如下图中3。我们从Debug目录下,再次打开应用程序,则应用程序的运行结果如下图中4。说明主程序1,已经把互斥量释放。

注意:具名互斥量是全局的操作系统对象。请务必正确关闭互斥量。最好使用using来包裹互斥量代码。这种方式可以在不同程序中同步线程。

三、使用SemaphoreSlim类

SemaphoreSlim是Semaphore类的一个轻量级版本。此类限制了同时访问同一资源的线程数量。

在.net中,类Semaphore封装了CLR中的内核同步对象。与标准的排他锁对象(Monitor,Mutex,SpinLock)不同的是,它不是一个排他的锁对象,它与SemaphoreSlim,ReaderWriteLock等一样允许多个有限的线程同时访问共享内存资源。

Semaphore就好像一个栅栏,有一定的容量,当里面的线程数量到达设置的最大值时候,就没有线程可以进去。然后,如果一个线程工作完成以后出来了,那下一个线程就可以进去了。Semaphore的WaitOne或Release等操作分别将自动地递减或者递增信号量的当前计数值。当线程试图对计数值已经为0的信号量执行WaitOne操作时,线程将阻塞直到计数值大于0。在构造Semaphore时,最少需要2个参数。信号量的初始容量和最大的容量。

1.程序代码

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading; //引入线程
  6. using System.Diagnostics;
  7. namespace ThreadSynchronousDemo
  8. {
  9. class Program
  10. {
  11. static SemaphoreSlim semapSlim = new SemaphoreSlim(5);
  12. static void Main(string[] args)
  13. {
  14. Console.WriteLine("开始,SemaphoreSlim 同步");
  15. for (int i = 1; i < 9; i++)
  16. {
  17. string threadName = "线程 " + i;
  18. int seconds = new Random().Next(1, 10);
  19. var t = new Thread((() => AccessDatabase(threadName, seconds)));
  20. t.Start();
  21. }
  22. Console.Read();
  23. }
  24. static void AccessDatabase(string name,int seconds)
  25. {
  26. Console.WriteLine("{0} 等待访问数据库", name);
  27. semapSlim.Wait();
  28. Console.WriteLine("{0} 被授予对数据库的访问权限", name);
  29. Thread.Sleep(TimeSpan.FromSeconds(seconds));
  30. Console.WriteLine("{0} 完成了", name);
  31. semapSlim.Release();
  32. }
  33. }
  34. }
复制代码

2.程序运行结果如下图。

当程序启动时,创建了一个SemaphoreSlim对象,并在构造函数中指定了并发的线程数量,然后启动了10个不同名称,不同初始运行时间的线程。

每个线程都尝试获取数据库访问权限,但是我们使用SemaphoreSlim对象做了限制,只有5个线程能同时访问数据库,当前5个线程获取了数据库访问权限之后,剩下的5个线程只能等待,直到有线程完成工作,并调用Semaphore的Release方法。



太阳http代理AD
回复

使用道具 举报

关闭

站长推荐上一条 /1 下一条