C#线程同步--限量使用
问题抽象:当某一资源同一时刻允许一定数量的线程使用的时候,需要有个机制来阻塞多余的线程,直到资源再次变得可用。
线程同步方案:Semaphore、SemaphoreSlim、CountdownEvent
方案特性:限量供应;除所有者外,其他人无条件等待;先到先得,没有先后顺序
1、Semaphore类
用于控制线程的访问数量,默认的构造函数为initialCount和maximumCount,表示默认设置的信号量个数和最大信号量个数。当你WaitOne的时候,信号量自减,当Release的时候,信号量自增,然而当信号量为0的时候,后续的线程就不能拿到WaitOne了,所以必须等待先前的线程通过Release来释放。
using System;
using System.Threading; namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
Thread t1 = new Thread(Run1);
t1.Start();
Thread t2 = new Thread(Run2);
t2.Start();
Thread t3 = new Thread(Run3);
t3.Start();
Console.ReadKey();
} //初始可以授予2个线程信号,因为第3个要等待前面的Release才能得到信号
static Semaphore sem = new Semaphore(, ); static void Run1()
{
sem.WaitOne();
Console.WriteLine("大家好,我是Run1;" + DateTime.Now.ToString("mm:ss")); //两秒后
Thread.Sleep();
sem.Release();
} static void Run2()
{
sem.WaitOne();
Console.WriteLine("大家好,我是Run2;" + DateTime.Now.ToString("mm:ss")); //两秒后
Thread.Sleep();
sem.Release();
} static void Run3()
{
sem.WaitOne();
Console.WriteLine("大家好,我是Run3;" + DateTime.Now.ToString("mm:ss")); //两秒后
Thread.Sleep();
sem.Release();
}
}
}
Program
在以上的方法中Release()方法相当于自增一个信号量,Release(5)自增5个信号量。但是,Release()到构造函数的第二个参数maximumCount的值就不能再自增了。
Semaphore可用于进程级交互。
using System;
using System.Diagnostics;
using System.Threading; namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{ Thread t1 = new Thread(Run1);
t1.Start(); Thread t2 = new Thread(Run2);
t2.Start(); Console.Read();
} //初始可以授予2个线程信号,因为第3个要等待前面的Release才能得到信号
static Semaphore sem = new Semaphore(, , "命名Semaphore"); static void Run1()
{
sem.WaitOne(); Console.WriteLine("进程:" + Process.GetCurrentProcess().Id + " 我是Run1" + DateTime.Now.TimeOfDay);
} static void Run2()
{
sem.WaitOne(); Console.WriteLine("进程:" + Process.GetCurrentProcess().Id + " 我是Run2" + DateTime.Now.TimeOfDay);
}
}
}
Program

直接运行两次bin目录的exe文件,就能发现最多只能输出3个。
图书馆都配备有若干台公用计算机供读者查询信息,当某日读者比较多时,必须排队等候。UseLibraryComputer实例用多线程模拟了多人使用多台计算机的过程
using System;
using System.Threading; namespace ConsoleApp1
{
class Program
{
//图书馆拥有的公用计算机
private const int ComputerNum = ;
private static Computer[] LibraryComputers;
//同步信号量
public static Semaphore sp = new Semaphore(ComputerNum, ComputerNum); static void Main(string[] args)
{
//图书馆拥有ComputerNum台电脑
LibraryComputers = new Computer[ComputerNum];
for (int i = ; i < ComputerNum; i++)
LibraryComputers[i] = new Computer("Computer" + (i + ).ToString());
int peopleNum = ;
Random ran = new Random();
Thread user;
System.Console.WriteLine("敲任意键模拟一批批的人排队使用{0}台计算机,ESC键结束模拟……", ComputerNum);
//每次创建若干个线程,模拟人排队使用计算机
while (System.Console.ReadKey().Key != ConsoleKey.Escape)
{
peopleNum = ran.Next(, );
System.Console.WriteLine("\n有{0}人在等待使用计算机。", peopleNum); for (int i = ; i <= peopleNum; i++)
{
user = new Thread(UseComputer);
user.Start("User" + i.ToString());
}
}
} //线程函数
static void UseComputer(Object UserName)
{
sp.WaitOne();//等待计算机可用 //查找可用的计算机
Computer cp = null;
for (int i = ; i < ComputerNum; i++)
if (LibraryComputers[i].IsOccupied == false)
{
cp = LibraryComputers[i];
break;
}
//使用计算机工作
cp.Use(UserName.ToString()); //不再使用计算机,让出来给其他人使用
sp.Release();
}
} class Computer
{
public readonly string ComputerName = "";
public Computer(string Name)
{
ComputerName = Name;
}
//是否被占用
public bool IsOccupied = false;
//人在使用计算机
public void Use(String userName)
{
System.Console.WriteLine("{0}开始使用计算机{1}", userName, ComputerName);
IsOccupied = true;
Thread.Sleep(new Random().Next(, )); //随机休眠,以模拟人使用计算机
System.Console.WriteLine("{0}结束使用计算机{1}", userName, ComputerName);
IsOccupied = false;
}
}
}
Program
using System;
using System.Threading;
using System.Threading.Tasks; namespace ConsoleApp1
{
class Program
{
static SemaphoreSlim slim = new SemaphoreSlim(Environment.ProcessorCount, ); static void Main(string[] args)
{
for (int i = ; i < ; i++)
{
Task.Factory.StartNew((obj) =>
{
Run(obj);
}, i);
}
Console.Read();
} static void Run(object obj)
{
slim.Wait();
Console.WriteLine("当前时间:{0}任务 {1}已经进入。", DateTime.Now, obj);
//这里busy3s中
Thread.Sleep();
slim.Release();
}
}
}
Program
同样,防止死锁的情况,我们需要知道”超时和取消标记“的解决方案,像SemaphoreSlim这种定死的”线程请求范围“,其实是降低了扩展性,使用需谨慎,在觉得有必要的时候使用它
using System;
using System.Threading;
using System.Threading.Tasks; namespace ConsoleApp1
{
class Program
{
//默认的容纳大小为“硬件线程“数
static CountdownEvent cde = new CountdownEvent(Environment.ProcessorCount); static void LoadUser(object obj)
{
try
{
Console.WriteLine("ThreadId={0};当前任务:{1}正在加载User部分数据!", Thread.CurrentThread.ManagedThreadId, obj);
}
finally
{
cde.Signal();
}
} static void LoadProduct(object obj)
{
try
{
Console.WriteLine("ThreadId={0};当前任务:{1}正在加载Product部分数据!", Thread.CurrentThread.ManagedThreadId, obj);
}
finally
{
cde.Signal();
}
} static void LoadOrder(object obj)
{
try
{
Console.WriteLine("ThreadId={0};当前任务:{1}正在加载Order部分数据!", Thread.CurrentThread.ManagedThreadId, obj);
}
finally
{
cde.Signal();
}
} static void Main(string[] args)
{
//加载User表需要5个任务
var userTaskCount = ;
//重置信号
cde.Reset(userTaskCount);
for (int i = ; i < userTaskCount; i++)
{
Task.Factory.StartNew((obj) =>
{
LoadUser(obj);
}, i);
}
//等待所有任务执行完毕
cde.Wait();
Console.WriteLine("\nUser表数据全部加载完毕!\n"); //加载product需要8个任务
var productTaskCount = ;
//重置信号
cde.Reset(productTaskCount);
for (int i = ; i < productTaskCount; i++)
{
Task.Factory.StartNew((obj) =>
{
LoadProduct(obj);
}, i);
}
cde.Wait();
Console.WriteLine("\nProduct表数据全部加载完毕!\n"); //加载order需要12个任务
var orderTaskCount = ;
//重置信号
cde.Reset(orderTaskCount);
for (int i = ; i < orderTaskCount; i++)
{
Task.Factory.StartNew((obj) =>
{
LoadOrder(obj);
}, i);
}
cde.Wait();
Console.WriteLine("\nOrder表数据全部加载完毕!\n"); Console.WriteLine("\n(*^__^*) 嘻嘻,恭喜你,数据全部加载完毕\n");
Console.Read();
}
}
}
Program
我们看到有两个主要方法:Wait和Signal。每调用一次Signal相当于麻将桌上走了一个人,直到所有人都搓过麻将wait才给放行,这里同样要注意也就是“超时“问题的存在性,尤其是在并行计算中,轻量级别给我们提供了”取消标记“的机制,这是在重量级别中不存在的
注:如果调用Signal()没有到达指定的次数,那么Wait()将一直等待,请确保使用每个线程完成后都要调用Signal方法。
C#线程同步--限量使用的更多相关文章
- [ 高并发]Java高并发编程系列第二篇--线程同步
高并发,听起来高大上的一个词汇,在身处于互联网潮的社会大趋势下,高并发赋予了更多的传奇色彩.首先,我们可以看到很多招聘中,会提到有高并发项目者优先.高并发,意味着,你的前雇主,有很大的业务层面的需求, ...
- C#多线程之线程同步篇3
在上一篇C#多线程之线程同步篇2中,我们主要学习了AutoResetEvent构造.ManualResetEventSlim构造和CountdownEvent构造,在这一篇中,我们将学习Barrier ...
- C#多线程之线程同步篇2
在上一篇C#多线程之线程同步篇1中,我们主要学习了执行基本的原子操作.使用Mutex构造以及SemaphoreSlim构造,在这一篇中我们主要学习如何使用AutoResetEvent构造.Manual ...
- C#多线程之线程同步篇1
在多线程(线程同步)中,我们将学习多线程中操作共享资源的技术,学习到的知识点如下所示: 执行基本的原子操作 使用Mutex构造 使用SemaphoreSlim构造 使用AutoResetEvent构造 ...
- C# 线程同步的三类情景
C# 已经提供了我们几种非常好用的类库如 BackgroundWorker.Thread.Task等,借助它们,我们就能够分分钟编写出一个多线程的应用程序. 比如这样一个需求:有一个 Winform ...
- Java进击C#——语法之线程同步
上一章我们讲到关于C#线程方向的应用.但是笔者并没有讲到多线程中的另一个知识点--同步.多线程的应用开发都有可能发生脏数据.同步的功能或多或少都会用到.本章就要来讲一下关于线程同步的问题.根据笔者这几 ...
- Java多线程 3 线程同步
在之前,已经学习到了线程的创建和状态控制,但是每个线程之间几乎都没有什么太大的联系.可是有的时候,可能存在多个线程多同一个数据进行操作,这样,可能就会引用各种奇怪的问题.现在就来学习多线程对数据访问的 ...
- JAVA之线程同步的三种方法
最近接触到一个图片加载的项目,其中有声明到的线程池等资源需要在系统中线程共享,所以就去研究了一下线程同步的知识,总结了三种常用的线程同步的方法,特来与大家分享一下.这三种方法分别是:synchroni ...
- 三、线程同步之Sysnchronized关键字
线程同步 问题引入 观察一面一段小程序: public class Main { private static int amount = 0; public static void main(Stri ...
随机推荐
- ubuntu hadoop环境搭建
安装Ubuntu系统:这个自行安装 下载jdk:我下的是1.8.0_141d的,下载好后在usr/lib下新建一个jvm的文件夹用来存放Java的文件,下载好的jdk可以在其他地方解压或者jvm里面解 ...
- Postman导出Api文档
一.最近离职要把做搞过的接口整理成文档,查了查postman好像不支持导出文档,于是写了个工具类,供大家参考! 前提你要先把postman里的接口导出来 如图: 二.所用到的包(主要Json相关的包) ...
- 【腾讯Bugly干货分享】经典随机Crash之一:线程安全
本文作者:鲁可--腾讯SNG专项测试组 测试工程师 背景 Android QQ 在2016下半年连着好几个版本二灰 Crash 率都很高,如果说有新需求,一灰的 Crash 率高,还能找点理由,可是开 ...
- Jquery中attr()与prop()的区别
在jQuery中,attr()函数和prop()函数都用于设置或获取指定的属性,它们的参数和用法也几乎完全相同.但是,这两个函数的用处却并不相同.下面我们来详细介绍这两个函数之间的区别. 1.操作对象 ...
- IDEA环境下GIT操作浅析之一Idea下仓库初始化与文件提交涉及到的基本命令
目标总括 idea 下通过命令操作文件提交,删除,与更新并推送到github 开源库基本操作idea 下通过命令实现分支的创建与合并操作 idea 下通过图形化方式实现idea 项目版本控制基本操作 ...
- Drools规则引擎入门指南(三)——使用Docker部署Workbench
其实本来我也是打算使用Tomcat来部署Workbench的,但是在网上看了几篇文章,超级繁琐的配置.各种版本.实在看不下去了索性就直接使用Docker来部署了.本次部署的版本是最新稳定版,对应dro ...
- openjtag 的硬件连接踩坑历程
这个事情踩了不少坑,这个要记录一下: 1 代码的下载和编译按照下面的来:https://github.com/pulp-platform/pulp-debug-bridge 注意点: A 要最好使用p ...
- 【Spark调优】内存模型与参数调优
[Spark内存模型] Spark在一个executor中的内存分为3块:storage内存.execution内存.other内存. 1. storage内存:存储broadcast,cache,p ...
- Vue : Expected the Promise rejection reason to be an Error
在vue项目中添加ESLint,new 一个 Promise 一直显示错误 :Expected the Promise rejection reason to be an Error 正常来说new ...
- 【Java提高】---通过UUID、SHA-1、Base64组合加密
通过UUID.SHA-1.Base64组合加密 该篇文章实现的最终效果是: 1)加密是不可逆的. 2)相同字符串加密产生后的字符串都不一样 3)所以要想比较两个字符串是否相等,需要用已经加过密的字符串 ...