问题抽象:当某一资源同一时刻允许一定数量的线程使用的时候,需要有个机制来阻塞多余的线程,直到资源再次变得可用。
线程同步方案:Semaphore、SemaphoreSlim、CountdownEvent
方案特性:限量供应;除所有者外,其他人无条件等待;先到先得,没有先后顺序

1、Semaphore类
      用于控制线程的访问数量,默认的构造函数为initialCount和maximumCount,表示默认设置的信号量个数和最大信号量个数。当你WaitOne的时候,信号量自减,当Release的时候,信号量自增,然而当信号量为0的时候,后续的线程就不能拿到WaitOne了,所以必须等待先前的线程通过Release来释放。

  1. using System;
  2. using System.Threading;
  3.  
  4. namespace ConsoleApp1
  5. {
  6. class Program
  7. {
  8. static void Main(string[] args)
  9. {
  10. Thread t1 = new Thread(Run1);
  11. t1.Start();
  12. Thread t2 = new Thread(Run2);
  13. t2.Start();
  14. Thread t3 = new Thread(Run3);
  15. t3.Start();
  16. Console.ReadKey();
  17. }
  18.  
  19. //初始可以授予2个线程信号,因为第3个要等待前面的Release才能得到信号
  20. static Semaphore sem = new Semaphore(, );
  21.  
  22. static void Run1()
  23. {
  24. sem.WaitOne();
  25. Console.WriteLine("大家好,我是Run1;" + DateTime.Now.ToString("mm:ss"));
  26.  
  27. //两秒后
  28. Thread.Sleep();
  29. sem.Release();
  30. }
  31.  
  32. static void Run2()
  33. {
  34. sem.WaitOne();
  35. Console.WriteLine("大家好,我是Run2;" + DateTime.Now.ToString("mm:ss"));
  36.  
  37. //两秒后
  38. Thread.Sleep();
  39. sem.Release();
  40. }
  41.  
  42. static void Run3()
  43. {
  44. sem.WaitOne();
  45. Console.WriteLine("大家好,我是Run3;" + DateTime.Now.ToString("mm:ss"));
  46.  
  47. //两秒后
  48. Thread.Sleep();
  49. sem.Release();
  50. }
  51. }
  52. }

Program

在以上的方法中Release()方法相当于自增一个信号量,Release(5)自增5个信号量。但是,Release()到构造函数的第二个参数maximumCount的值就不能再自增了。

Semaphore可用于进程级交互。

  1. using System;
  2. using System.Diagnostics;
  3. using System.Threading;
  4.  
  5. namespace ConsoleApp1
  6. {
  7. class Program
  8. {
  9. static void Main(string[] args)
  10. {
  11.  
  12. Thread t1 = new Thread(Run1);
  13. t1.Start();
  14.  
  15. Thread t2 = new Thread(Run2);
  16. t2.Start();
  17.  
  18. Console.Read();
  19. }
  20.  
  21. //初始可以授予2个线程信号,因为第3个要等待前面的Release才能得到信号
  22. static Semaphore sem = new Semaphore(, , "命名Semaphore");
  23.  
  24. static void Run1()
  25. {
  26. sem.WaitOne();
  27.  
  28. Console.WriteLine("进程:" + Process.GetCurrentProcess().Id + " 我是Run1" + DateTime.Now.TimeOfDay);
  29. }
  30.  
  31. static void Run2()
  32. {
  33. sem.WaitOne();
  34.  
  35. Console.WriteLine("进程:" + Process.GetCurrentProcess().Id + " 我是Run2" + DateTime.Now.TimeOfDay);
  36. }
  37. }
  38. }

Program

直接运行两次bin目录的exe文件,就能发现最多只能输出3个。

Semaphore可以限制可同时访问某一资源或资源池的线程数。
        Semaphore类在内部维护一个计数器,当一个线程调用Semaphore对象的Wait系列方法时,此计数器减一,只要计数器还是一个正数,线程就不会阻塞。当计数器减到0时,再调用Semaphore对象Wait系列方法的线程将被阻塞,直到有线程调用Semaphore对象的Release()方法增加计数器值时,才有可能解除阻塞状态。
 
示例说明:
图书馆都配备有若干台公用计算机供读者查询信息,当某日读者比较多时,必须排队等候。UseLibraryComputer实例用多线程模拟了多人使用多台计算机的过程

  1. using System;
  2. using System.Threading;
  3.  
  4. namespace ConsoleApp1
  5. {
  6. class Program
  7. {
  8. //图书馆拥有的公用计算机
  9. private const int ComputerNum = ;
  10. private static Computer[] LibraryComputers;
  11. //同步信号量
  12. public static Semaphore sp = new Semaphore(ComputerNum, ComputerNum);
  13.  
  14. static void Main(string[] args)
  15. {
  16. //图书馆拥有ComputerNum台电脑
  17. LibraryComputers = new Computer[ComputerNum];
  18. for (int i = ; i < ComputerNum; i++)
  19. LibraryComputers[i] = new Computer("Computer" + (i + ).ToString());
  20. int peopleNum = ;
  21. Random ran = new Random();
  22. Thread user;
  23. System.Console.WriteLine("敲任意键模拟一批批的人排队使用{0}台计算机,ESC键结束模拟……", ComputerNum);
  24. //每次创建若干个线程,模拟人排队使用计算机
  25. while (System.Console.ReadKey().Key != ConsoleKey.Escape)
  26. {
  27. peopleNum = ran.Next(, );
  28. System.Console.WriteLine("\n有{0}人在等待使用计算机。", peopleNum);
  29.  
  30. for (int i = ; i <= peopleNum; i++)
  31. {
  32. user = new Thread(UseComputer);
  33. user.Start("User" + i.ToString());
  34. }
  35. }
  36. }
  37.  
  38. //线程函数
  39. static void UseComputer(Object UserName)
  40. {
  41. sp.WaitOne();//等待计算机可用
  42.  
  43. //查找可用的计算机
  44. Computer cp = null;
  45. for (int i = ; i < ComputerNum; i++)
  46. if (LibraryComputers[i].IsOccupied == false)
  47. {
  48. cp = LibraryComputers[i];
  49. break;
  50. }
  51. //使用计算机工作
  52. cp.Use(UserName.ToString());
  53.  
  54. //不再使用计算机,让出来给其他人使用
  55. sp.Release();
  56. }
  57. }
  58.  
  59. class Computer
  60. {
  61. public readonly string ComputerName = "";
  62. public Computer(string Name)
  63. {
  64. ComputerName = Name;
  65. }
  66. //是否被占用
  67. public bool IsOccupied = false;
  68. //人在使用计算机
  69. public void Use(String userName)
  70. {
  71. System.Console.WriteLine("{0}开始使用计算机{1}", userName, ComputerName);
  72. IsOccupied = true;
  73. Thread.Sleep(new Random().Next(, )); //随机休眠,以模拟人使用计算机
  74. System.Console.WriteLine("{0}结束使用计算机{1}", userName, ComputerName);
  75. IsOccupied = false;
  76. }
  77. }
  78. }

Program

2、SemaphoreSlim类
     在.net 4.0之前,framework中有一个重量级的Semaphore,可以跨进程同步,SemaphoreSlim轻量级不行,msdn对它的解释为:限制可同时访问某一资源或资源池的线程数。

  1. using System;
  2. using System.Threading;
  3. using System.Threading.Tasks;
  4.  
  5. namespace ConsoleApp1
  6. {
  7. class Program
  8. {
  9. static SemaphoreSlim slim = new SemaphoreSlim(Environment.ProcessorCount, );
  10.  
  11. static void Main(string[] args)
  12. {
  13. for (int i = ; i < ; i++)
  14. {
  15. Task.Factory.StartNew((obj) =>
  16. {
  17. Run(obj);
  18. }, i);
  19. }
  20. Console.Read();
  21. }
  22.  
  23. static void Run(object obj)
  24. {
  25. slim.Wait();
  26. Console.WriteLine("当前时间:{0}任务 {1}已经进入。", DateTime.Now, obj);
  27. //这里busy3s中
  28. Thread.Sleep();
  29. slim.Release();
  30. }
  31. }
  32. }

Program

同样,防止死锁的情况,我们需要知道”超时和取消标记“的解决方案,像SemaphoreSlim这种定死的”线程请求范围“,其实是降低了扩展性,使用需谨慎,在觉得有必要的时候使用它

注:Semaphore类是SemaphoreSlim类的老版本,该版本使用纯粹的内核时间(kernel-time)方式。
    SemaphoreSlim类不使用Windows内核信号量,而且也不支持进程间同步。所以在跨程序同步的场景下可以使用Semaphore
 
3、CountdownEvent类
     这种采用信号状态的同步基元非常适合在动态的fork,join的场景,它采用“信号计数”的方式,就比如这样,一个麻将桌只能容纳4个人打麻将,如果后来的人也想搓一把碰碰运气,那么他必须等待直到麻将桌上的人走掉一位。好,这就是简单的信号计数机制,从技术角度上来说它是定义了最多能够进入关键代码的线程数。
     但是CountdownEvent更牛X之处在于我们可以动态的改变“信号计数”的大小,比如一会儿能够容纳8个线程,一下又4个,一下又10个,这样做有什么好处呢?比如一个任务需要加载1w条数据,那么可能出现这种情况。
例如:
加载User表:         根据user表的数据量,我们需要开5个task。
加载Product表:    产品表数据相对比较多,计算之后需要开8个task。
加载order表:       由于我的网站订单丰富,计算之后需要开12个task。

  1. using System;
  2. using System.Threading;
  3. using System.Threading.Tasks;
  4.  
  5. namespace ConsoleApp1
  6. {
  7. class Program
  8. {
  9. //默认的容纳大小为“硬件线程“数
  10. static CountdownEvent cde = new CountdownEvent(Environment.ProcessorCount);
  11.  
  12. static void LoadUser(object obj)
  13. {
  14. try
  15. {
  16. Console.WriteLine("ThreadId={0};当前任务:{1}正在加载User部分数据!", Thread.CurrentThread.ManagedThreadId, obj);
  17. }
  18. finally
  19. {
  20. cde.Signal();
  21. }
  22. }
  23.  
  24. static void LoadProduct(object obj)
  25. {
  26. try
  27. {
  28. Console.WriteLine("ThreadId={0};当前任务:{1}正在加载Product部分数据!", Thread.CurrentThread.ManagedThreadId, obj);
  29. }
  30. finally
  31. {
  32. cde.Signal();
  33. }
  34. }
  35.  
  36. static void LoadOrder(object obj)
  37. {
  38. try
  39. {
  40. Console.WriteLine("ThreadId={0};当前任务:{1}正在加载Order部分数据!", Thread.CurrentThread.ManagedThreadId, obj);
  41. }
  42. finally
  43. {
  44. cde.Signal();
  45. }
  46. }
  47.  
  48. static void Main(string[] args)
  49. {
  50. //加载User表需要5个任务
  51. var userTaskCount = ;
  52. //重置信号
  53. cde.Reset(userTaskCount);
  54. for (int i = ; i < userTaskCount; i++)
  55. {
  56. Task.Factory.StartNew((obj) =>
  57. {
  58. LoadUser(obj);
  59. }, i);
  60. }
  61. //等待所有任务执行完毕
  62. cde.Wait();
  63. Console.WriteLine("\nUser表数据全部加载完毕!\n");
  64.  
  65. //加载product需要8个任务
  66. var productTaskCount = ;
  67. //重置信号
  68. cde.Reset(productTaskCount);
  69. for (int i = ; i < productTaskCount; i++)
  70. {
  71. Task.Factory.StartNew((obj) =>
  72. {
  73. LoadProduct(obj);
  74. }, i);
  75. }
  76. cde.Wait();
  77. Console.WriteLine("\nProduct表数据全部加载完毕!\n");
  78.  
  79. //加载order需要12个任务
  80. var orderTaskCount = ;
  81. //重置信号
  82. cde.Reset(orderTaskCount);
  83. for (int i = ; i < orderTaskCount; i++)
  84. {
  85. Task.Factory.StartNew((obj) =>
  86. {
  87. LoadOrder(obj);
  88. }, i);
  89. }
  90. cde.Wait();
  91. Console.WriteLine("\nOrder表数据全部加载完毕!\n");
  92.  
  93. Console.WriteLine("\n(*^__^*) 嘻嘻,恭喜你,数据全部加载完毕\n");
  94. Console.Read();
  95. }
  96. }
  97. }

Program

我们看到有两个主要方法:Wait和Signal。每调用一次Signal相当于麻将桌上走了一个人,直到所有人都搓过麻将wait才给放行,这里同样要注意也就是“超时“问题的存在性,尤其是在并行计算中,轻量级别给我们提供了”取消标记“的机制,这是在重量级别中不存在的

注:如果调用Signal()没有到达指定的次数,那么Wait()将一直等待,请确保使用每个线程完成后都要调用Signal方法。

C#线程同步--限量使用的更多相关文章

  1. [ 高并发]Java高并发编程系列第二篇--线程同步

    高并发,听起来高大上的一个词汇,在身处于互联网潮的社会大趋势下,高并发赋予了更多的传奇色彩.首先,我们可以看到很多招聘中,会提到有高并发项目者优先.高并发,意味着,你的前雇主,有很大的业务层面的需求, ...

  2. C#多线程之线程同步篇3

    在上一篇C#多线程之线程同步篇2中,我们主要学习了AutoResetEvent构造.ManualResetEventSlim构造和CountdownEvent构造,在这一篇中,我们将学习Barrier ...

  3. C#多线程之线程同步篇2

    在上一篇C#多线程之线程同步篇1中,我们主要学习了执行基本的原子操作.使用Mutex构造以及SemaphoreSlim构造,在这一篇中我们主要学习如何使用AutoResetEvent构造.Manual ...

  4. C#多线程之线程同步篇1

    在多线程(线程同步)中,我们将学习多线程中操作共享资源的技术,学习到的知识点如下所示: 执行基本的原子操作 使用Mutex构造 使用SemaphoreSlim构造 使用AutoResetEvent构造 ...

  5. C# 线程同步的三类情景

    C# 已经提供了我们几种非常好用的类库如 BackgroundWorker.Thread.Task等,借助它们,我们就能够分分钟编写出一个多线程的应用程序. 比如这样一个需求:有一个 Winform ...

  6. Java进击C#——语法之线程同步

    上一章我们讲到关于C#线程方向的应用.但是笔者并没有讲到多线程中的另一个知识点--同步.多线程的应用开发都有可能发生脏数据.同步的功能或多或少都会用到.本章就要来讲一下关于线程同步的问题.根据笔者这几 ...

  7. Java多线程 3 线程同步

    在之前,已经学习到了线程的创建和状态控制,但是每个线程之间几乎都没有什么太大的联系.可是有的时候,可能存在多个线程多同一个数据进行操作,这样,可能就会引用各种奇怪的问题.现在就来学习多线程对数据访问的 ...

  8. JAVA之线程同步的三种方法

    最近接触到一个图片加载的项目,其中有声明到的线程池等资源需要在系统中线程共享,所以就去研究了一下线程同步的知识,总结了三种常用的线程同步的方法,特来与大家分享一下.这三种方法分别是:synchroni ...

  9. 三、线程同步之Sysnchronized关键字

    线程同步 问题引入 观察一面一段小程序: public class Main { private static int amount = 0; public static void main(Stri ...

随机推荐

  1. Python的基本数据类型

    数据类型常用函数 type(a)-得到变量a的数据类型 isinstance(a,str)-判断a是否是字符串类型 Python中有五个标准数据类型 数字Number 字符串String 数组List ...

  2. Centos6.5 安装MYSQL 5.5 -5.6.-5.7 一键yum快速安装 ,初始配置

    Centos6.5 安装MYSQL 5.5 ---5.6---5.7 一键yum快速安装 ,初始配置 第一步:安装mysql-5.5---- 5.6 ---- 5.7的yum源 [root@sv03 ...

  3. 【腾讯Bugly干货分享】那些年,我们一起写过的“单例模式”

    题记 度娘上对设计模式(Design pattern)的定义是:"一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结."它由著名的"四人帮",又称 ...

  4. java数据结构面试问题—快慢指针问题

    上次我们学习了环形链表的数据结构,那么接下来我们来一起看看下面的问题, 判断一个单向链表是否是环形链表? 看到这个问题,有人就提出了进行遍历链表,记住第一元素,当我们遍历后元素再次出现则是说明是环形链 ...

  5. 前端切图神器-cutterman

    之前我写过一篇关于前端切图的博客:https://www.cnblogs.com/tu-0718/p/9741058.html 但上面的方法在切图量大时依然很费时间,下面向大家推荐这款免费切图神器 c ...

  6. SpringBoot开源项目(企业信息化基础平台)

    JEEPlatform 一款企业信息化开发基础平台,可以用于快速构建企业后台管理系统,集成了OA(办公自动化).SCM(供应链系统).ERP(企业资源管理系统).CMS(内容管理系统).CRM(客户关 ...

  7. txt文本处理---行未添加逗号

    做音频处理过程中,经常遇到需要对文本进行转换,今天就遇到了一个行末加逗号的问题,找到了几种有效的方式,做个记录吧. 以下是几种方法实现: python代码实现: import os with open ...

  8. remote: Incorrect username or password ( access token )

    解决问题 进入控制面板 用户账号,选择管理您的凭据 修改凭据 修改完成后,保存即可

  9. yolov3中 预测的bbox如何从特征图映射到原图?

    Anchor Box的边框 选取标准的k-means(欧式距离来衡量差异),在box的尺寸比较大的时候其误差也更大,而我们希望的是误差和box的尺寸没有太大关系.所以通过IOU定义了如下的距离函数,使 ...

  10. 安全测试之sql注入

    不管是web界面还是app,都会涉及表单输入和提交,如果程序员没有对提交的字符进行过滤或者特殊处理,很容易会产生问题,这里讲的的sql注入就是其中一种方式,在表单中输入sql语句达到进入系统的目的. ...