C#多线程间的同步问题
使用线程时最头痛的就是共享资源的同步问题,处理不好会得到错误的结果,C#处理共享资源有以下几种:
1、lock锁
需要注意的地方:
1).lock不能锁定空值某一对象可以指向Null,但Null是不需要被释放的。(请参考:认识全面的null)
2).lock不能锁定string类型,虽然它也是引用类型的。因为字符串类型被CLR“暂留”
这意味着整个程序中任何给定字符串都只有一个实例,就是这同一个对象表示了所有运行的应用程序域的所有线程中的该文本。因此,只要在应用程序进程中
的任何位置处具有相同内容的字符串上放置了锁,就将锁定应用程序中该字符串的所有实例。因此,最好锁定不会被暂留的私有或受保护成员。
3).lock锁定的对象是一个程序块的内存边界
4).值类型不能被lock,因为前文标红字的“对象被释放”
,值类型不是引用类型的
5).lock就避免锁定public 类型或不受程序控制的对象。
应用场景:经常会应用于防止多线程操作导致公用变量值出现不确定的异常,用于确保操作的安全性
2、
互斥锁(Mutex)
互斥锁是一个互斥的同步对象,意味着同一时间有且仅有一个线程可以获取它。
互斥锁可适用于一个共享资源每次只能被一个线程访问的情况
我们可以把Mutex看作一个出租车,乘客看作线程。乘客首先等车,然后上车,最后下车。当一个乘客在车上时,其他乘客就只有等他下车以后才可以上车。而 线程与C# Mutex对象的关系也正是如此,线程使用Mutex.WaitOne()方法等待C# Mutex对象被释放,如果它等待的C# Mutex对象被释放了,它就自动拥有这个对象,直到它调用Mutex.ReleaseMutex()方法释放这个对象,而在此期间,其他想要获取这个 C# Mutex对象的线程都只有等待。
如果要获取一个互斥锁。应调用互斥锁上的WaitOne()方法,该方法继承于Thread.WaitHandle类
它处于等到状态直至所调用互斥锁可以被获取,因此该方法将组织住主调线程直到指定的互斥锁可用,如果不需要拥有互斥锁,用ReleaseMutex方法释放,从而使互斥锁可以被另外一个线程所获取.
3、semaphore
其中lock 和mutex 差不多,都是锁定同一个资源,不同之处mutex在整个进程中都可以访问到。
而semaphore是锁定多个资源,比如同一时期只能有两个线程访问,其它线程只能等待其中之一释放锁才能使用,Semaphore就是一个可以多次进入的“Mutex”。Mutex永远只允许一个线程拥有它,而Semaphore可以允许多个线程请求,因此Semaphore被用于管理一次可以允许多个线程进入并发访问资源的情况。
下面是一个简单的例子,:
class Program
{
static Semaphore sp = new Semaphore(,);
static void Main(string[] args)
{ DoWork();
Console.Read();
} private static void DoWork()
{
for (int i = ; i < ; i++)
{
Task.Run(() => {
sp.WaitOne();
Console.WriteLine("线程:"+Thread.CurrentThread.ManagedThreadId+",开始运行");
Thread.Sleep(new Random().Next());
Console.WriteLine("线程:" + Thread.CurrentThread.ManagedThreadId + ",结束此运行");
sp.Release();
});
}
}
}
另举一个复杂一些的例子:学生都去图书馆查资料,图书馆共有3台电脑,如果来的人超过3人则需要排队等待,此例子中还要注意一点,那个学生选择那台电脑,学生找空闲电脑用Mutex锁定电脑对象,否则定位的电脑可能是错误的(可能会出现多名同学使用同一台电脑的情况,使用mutex锁定资源,这样才能确保一台空闲电脑只能是一名学生选择)
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);
Task[] ts = new Task[peopleNum];
for (int i = ; i <peopleNum; i++)
{
int n = i+;
ts[i]=Task.Run(() => { UseComputer("User" + n.ToString()); });
}
Task.WaitAll(ts);
Console.WriteLine("All threads finished!");
} }
static Mutex m = new Mutex(); //线程函数
static void UseComputer(Object UserName)
{
sp.WaitOne();//等待计算机可用 //查找可用的计算机
Computer cp = null;
m.WaitOne();
for (int i = ; i < ComputerNum; i++)
{
if (LibraryComputers[i].IsOccupied == false)
{
LibraryComputers[i].IsOccupied = true;
cp = LibraryComputers[i];
break;
}
}
m.ReleaseMutex();
//使用计算机工作
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);
Thread.Sleep(new Random().Next(, )); //随机休眠,以模拟人使用计算机
System.Console.WriteLine("{0}结束使用计算机{1}", userName, ComputerName);
IsOccupied = false;
}
}
4.
AutoResetEvent 允许线程通过发信号互相通信。通常,此通信涉及线程需要独占访问的资源。
线程通过调用 AutoResetEvent 上的 WaitOne 来等待信号。如果 AutoResetEvent 处于非终止状态,则该线程阻塞,并等待当前控制资源的线程
通过调用 Set 发出资源可用的信号。
调用 Set 向 AutoResetEvent 发信号以释放等待线程。AutoResetEvent 将保持终止状态,直到一个正在等待的线程被释放,然后自动返回非终止状态。如果没有任何线程在等待,则状态将无限期地保持为终止状态。
可以通过将一个布尔值传递给构造函数来控制 AutoResetEvent 的初始状态,如果初始状态为终止状态,则为 true;否则为 false。
举例:面试时,每次叫一个,只能一个人进去。
class Program
{
static AutoResetEvent are = new AutoResetEvent(false);
static void Main(string[] args)
{
//10个人排队,叫一声,进一个
for (int i = ; i < ; i++)
{
Task.Run(() => {
are.WaitOne();
Console.WriteLine(Thread.CurrentThread.ManagedThreadId+"进门");
Thread.Sleep(new Random().Next());
Console.WriteLine(Thread.CurrentThread.ManagedThreadId + "出门"); });
} System.Console.WriteLine("G:放行,ESC:退出\n");
Action action = new Action(() =>
{
are.Set();
})
;
while (true)
{
ConsoleKey key = Console.ReadKey(true).Key; if (key == ConsoleKey.G)
action();
if (key == ConsoleKey.Escape)
{
break;
} }
}
}
5、ManualResetEvent
ManualResetEvent就像一个信号灯,可以利用它的信号,控制当前线程是挂起状态还是运行状态。
它有几个常用的方法:Reset(),Set(),WaitOne();
初始化该对象时,可以指定其默认的状态(有信号/无信号);
在初始化以后,该对象将保持原来的状态不变,直到它的Reset()或者Set()方法被调用;
Reset()方法将其设置为无信号状态,Set()方法将其设置为有信号状态;
WaitOne()方法在无信号状态下,可以使当前线程挂起;注意这里说的是当前线程;
直到调用了Set()方法,该线程才被激活。
在多线程的代码里,可以使用一个ManualResetEvent对象来控制线程所有线程;
只要在调用WaitOne()方法前,调用Reset()方法,因为WaitOne()控制的是当前线程;
但是这样做,ManualResetEvent对象的管理逻辑会变得复杂;
所以这里建议一条线程一个ManualResetEvent对象。
举例://模拟3辆汽车过红绿灯
class Program
{
static ManualResetEvent mre = new ManualResetEvent(false);
static void Main(string[] args)
{
//模拟3辆汽车过红绿灯
for (int i = ; i < ; i++)
{
Task.Run(() =>
{
int count = ;
while (true)
{
mre.WaitOne();
Thread.Sleep(new Random().Next());
count++;
Console.WriteLine(Thread.CurrentThread.ManagedThreadId + "第{0}次开始运行", count);
}
});
} Action stop = delegate()
{
mre.Reset();
Console.WriteLine("红灯");
};
Action go = delegate()
{
mre.Set();
Console.WriteLine("绿灯");
};
System.Console.WriteLine("G:绿灯,R:红灯\n"); while (true)
{
var k = Console.ReadKey(true).Key;
if (k == ConsoleKey.G)
{
go();
}
else if (k == ConsoleKey.R)
{
stop();
}
else
{
System.Console.WriteLine("G:绿灯,R:红灯\n");
}
}
}
}
C#多线程间的同步问题的更多相关文章
- c#中多线程间的同步
目录 一.引入 二.Lock 三.Monitor 四.Interlocked 五.Semaphore 六.Event 七.Barrier 八.ReaderWriterLockSlim 九.Mutex ...
- vc++高级班之多线程篇[6]---线程间的同步机制①
①.线程同步的必要性: int g_Num = 0; UINT __cdecl ThreadProc(LPVOID lpParameter) { for (int idx = 0; idx &l ...
- C# 多线程之线程同步
多线程间应尽量避免同步问题,最好不要线程间共享数据.如果必须要共享数据,就需要使用同步技术,确保一次只有一个线程访问和改变共享状态. 一::lock语句 lock语句事设置锁定和接触锁定的一种简单方法 ...
- vc++高级班之多线程篇[7]---线程间的同步机制②
//示例代码: CStringArray g_ArrString; UINT __cdecl ThreadProc(LPVOID lpParameter) { int startIdx = (int ...
- C#多线程之线程同步篇1
在多线程(线程同步)中,我们将学习多线程中操作共享资源的技术,学习到的知识点如下所示: 执行基本的原子操作 使用Mutex构造 使用SemaphoreSlim构造 使用AutoResetEvent构造 ...
- Java 多线程间的通讯
在前一小节,介绍了在多线程编程中使用同步机制的重要性,并学会了如何实现同步的方法来正确地访问共享资源.这些线程之间的关系是平等的,彼此之间并不存在任何依赖,它们各自竞争CPU资源,互不相让,并且还无条 ...
- C#当中的多线程_线程同步
第2章 线程同步 原来以为线程同步就是lock,monitor等呢,看了第二章真是大开眼界啊! 第一章中我们遇到了一个叫做竞争条件的问题.引起的原因是没有进行正确的线程同步.当一个线程在执行操作时候, ...
- C#多线程编程的同步也线程安全
前一篇文章记录了简单的多线程编程的几种方式,但是在实际的项目中,也需要等待多线程执行完成之后再执行的方法,这个就叫做多线程的同步,或者,由于多个线程对同一对象的同时操作造成数据错乱,需要线程安全.这篇 ...
- 数据结构(逻辑结构,物理结构,特点) C#多线程编程的同步也线程安全 C#多线程编程笔记 String 与 StringBuilder (StringBuffer) 数据结构与算法-初体验(极客专栏)
数据结构(逻辑结构,物理结构,特点) 一.数据的逻辑结构:指反映数据元素之间的逻辑关系的数据结构,其中的逻辑关系是指数据元素之间的前后件关系,而与他们在计算机中的存储位置无关.逻辑结构包括: 集合 数 ...
随机推荐
- [BZOJ4552][Tjoi2016&Heoi2016]排序(二分答案+线段树)
二分答案mid,将>=mid的设为1,<mid的设为0,这样排序就变成了区间修改的操作,维护一下区间和即可 然后询问第q个位置的值,为1说明>=mid,以上 时间复杂度O(nlog2 ...
- python--模块之random随机数模块
作用是产生随机数 import random random.random:用于生成一个0--1的随机浮点数. print(random.random())>>0.3355102133472 ...
- Java基础——注解
一.概述 引自百度百科: 定义:注解(Annotation),也叫元数据.一种代码级别的说明.它是JDK1.5及以后版本引入的一个特性,与类.接口.枚举是在同一个层次.它可以声明在包.类.字段.方法. ...
- 算法-PHP实现八大算法
八大算法原理详解 交换函数:注意要按引用传递,否则无法真正交换两个数的值 function exchange(&$a, &$b){ $temp = $a; $a = $b; $b = ...
- 成都Uber优步司机奖励政策(3月12日)
滴快车单单2.5倍,注册地址:http://www.udache.com/ 如何注册Uber司机(全国版最新最详细注册流程)/月入2万/不用抢单:http://www.cnblogs.com/mfry ...
- .net core 基于multipart/form-data的文件上传,这里以图片上传为例
首先传递的数据格式大概如下: 然后就可以在后端获取数据了:直接上代码了哈: [HttpPost] ///分别获取 data数据和调用图片上传方法 public async Task< ...
- ORB-SLAM(十)LoopClosing Sim3求解
主要参考这篇论文 Horn B K P. Closed-form solution of absolute orientation using unit quaternions[J]. JOSA A, ...
- “网易有钱”sketch使用分享
本文来自网易云社区 写在开头,关于ps与sketch之间的优劣网上已经有很多分享,大家有兴趣可以百度,其中对否我们在这里不予评价.在移动互联网时代每个app从几十到上百张页面,如果用ps绘制一个个页面 ...
- 「日常训练」Case of Matryoshkas(Codeforces Round #310 Div. 2 C)
题意与分析(CodeForces 556C) 为了将所有\(n\)个娃娃编号递增地串在一起(原先是若干个串,每个串是递增的), 我们有两种操作: 拆出当前串中最大编号的娃娃(且一定是最右边的娃娃). ...
- Python入门编程中的变量、字符串以及数据类型
//2018.10.10 字符串与变量 1. 在输出语句中如果需要出现单引号或者双引号,可以使用转义符号\,它可以将其中的歧义错误解释化解,使得输出正常: 2. 对于python的任何变量都需要进行赋 ...