平常在多线程开发中,总避免不了线程同步。本篇就对net多线程中的锁系统做个简单描述。
目录
一:lock、Monitor
1:基础。
2: 作用域。
3:字符串锁。
4:monitor使用
二:mutex
三:Semaphore
四:总结
一:lock、Monitor
1:基础
Lock是Monitor语法糖简化写法。Lock在IL会生成Monitor。
//======Example 1===== string obj = "helloworld"; lock (obj) { Console.WriteLine(obj); } //lock IL会编译成如下写法 bool isGetLock = false; Monitor.Enter(obj, ref isGetLock); try { Console.WriteLine(obj); } finally { if (isGetLock) { Monitor.Exit(obj); } }
isGetLock参数是Framework 4.0后新加的。 为了使程序在所有情况下都能够确定,是否有必要释放锁。例: Monitor.Enter拿不到锁
Monitor.Enter 是可以锁值类型的。锁时会装箱成新对象,所以无法做到线程同步。
2:作用域
一:Lock是只能在进程内锁,不能跨进程。走的是混合构造,先自旋再转成内核构造。
二:关于对type类型的锁。如下:
//======Example 2===== new Thread(new ThreadStart(() => { lock (typeof(int)) { Thread.Sleep(10000); Console.WriteLine("Thread1释放"); } })).Start(); Thread.Sleep(1000); lock(typeof(int)) { Console.WriteLine("Thread2释放"); }
运行结果如下:
我们在来看个例子。
//======Example 3===== Console.WriteLine(DateTime.Now); AppDomain appDomain1 = AppDomain.CreateDomain("AppDomain1"); LockTest Worker1 = (LockTest)appDomain1.CreateInstanceAndUnwrap( Assembly.GetExecutingAssembly().FullName, "ConsoleApplication1.LockTest"); Worker1.Run();AppDomain appDomain2 = AppDomain.CreateDomain("AppDomain2"); LockTest Worker2 = (LockTest)appDomain2.CreateInstanceAndUnwrap( Assembly.GetExecutingAssembly().FullName, "ConsoleApplication1.LockTest"); Worker2.Run(); /// <summary> /// 跨应用程序域边界或远程访问时需要继承MarshalByRefObject /// </summary> public class LockTest : MarshalByRefObject { public void Run() { lock (typeof(int)) { Thread.Sleep(10000); Console.WriteLine(AppDomain.CurrentDomain.FriendlyName + ": Thread 释放," + DateTime.Now); } } }
运行结果如下:
第一个例子说明,在同进程同域,不同线程下,锁type int,其实锁的是同一个int对象。所以要慎用。
第二个例子,这里就简单说下。
A: CLR启动时,会创建 系统域(System Domain)和共享域(Shared Domain), 默认程序域(Default AppDomain)。 系统域和共享域是单例的。程序域可以有多个,例子中我们使用AppDomain.CreateDomain方法创建的。
B: 按正常来说,每个程序域的代码都是隔离,互不影响的。但对于一些基础类型来说,每个程序域都重新加载一份,就显得有点浪费,带来额外的损耗压力。聪明的CLR会把一些基本类型Object, ValueType, Array, Enum, String, and Delegate等所在的程序集MSCorLib.dll,在CLR启动过程中都会加载到共享域。 每个程序域都会使用共享域的基础类型实例。
C: 而每个程序域都有属于自己的托管堆。托管堆中最重要的是GC heap和Loader heap。GC heap用于引用类型实例的存储,生命周期管理和垃圾回收。Loader heap保存类型系统,如MethodTable,数据结构等,Loader heap生命周期不受GC管理,跟程序域卸载有关。
所以共享域中Loader heap MSCorLib.dll中的int实例会一直保留着,直到进程结束。单个程序域卸载也不受影响。作用域很大有没有!!!
这时第二个例子也很容易理解了。 锁int实例是跨程序域的,MSCorLib中的基础类型都是这样。 极容易造成死锁,慎用。 而自定义类型则会加载到自己的程序域,不会影响别人。
3:字符串的锁
我们都知道锁的目的,是为了多线程下值被破坏。也知道string在c#是个特殊对象,值是不变的,每次变动都是一个新对象值,这也是推荐stringbuilder原因。如例:
//======Example 4===== string str1 = "mushroom"; string str2 = "mushroom"; var result1 = object.ReferenceEquals(str1, str2); var result2 = object.ReferenceEquals(str1, "mushroom"); Console.WriteLine(result1 + "-" + result2); /* output * True-True */
正式由于c#中字符串的这种特性,所以字符串是在多线程下是不会被修改的,只读的。它存在于SystemDomain域中managed heap中的一个hash table中。Key为string本身,Value为string对象的地址。
当程序域需要一个string的时候,CLR首先在这个Hashtable根据这个string的hash code试着找对应的Item。如果成功找到,则直接把对应的引用返回,否则就在SystemDomain对应的managed heap中创建该 string,并加入到hash table中,并把引用返回。所以说字符串的生命周期是基于整个进程的,也是跨AppDomain。
4:monitor用法
介绍下Wait,Pulse,PulseAll的用法。有注释,大家直接看代码吧。
static string str = "mushroom"; static void Main(string[] args) { new Thread(() => { bool isGetLock = false; Monitor.Enter(str, ref isGetLock); try { Console.WriteLine("Thread1第一次获取锁"); Thread.Sleep(5000); Console.WriteLine("Thread1暂时释放锁,并等待其他线程释放通知信号。"); Monitor.Wait(str); Console.WriteLine("Thread1接到通知,第二次获取锁。"); Thread.Sleep(1000); } finally { if (isGetLock) { Monitor.Exit(str); Console.WriteLine("Thread1释放锁"); } } }).Start(); Thread.Sleep(1000); new Thread(() => { bool isGetLock = false; Monitor.Enter(str, ref isGetLock); //一直等待中,直到其他释放。 try { Console.WriteLine("Thread2获得锁"); Thread.Sleep(5000); Monitor.Pulse(str); //通知队列里一个线程,改变锁状态。 Pulseall 通知所有的 Console.WriteLine("Thread2通知其他线程,改变状态。"); Thread.Sleep(1000); } finally { if (isGetLock) { Monitor.Exit(str); Console.WriteLine("Thread2释放锁"); } }}).Start(); Console.ReadLine();
二:mutex
lock是不能跨进程锁的。 mutex作用和lock类似,但是它能跨进程锁资源(走的是windows内核构造)。 我们来看个例子
static bool createNew = false; //第一个参数 是否应拥有互斥体的初始所属权。即createNew true时,mutex默认获得处理信号 //第二个是名字,第三个是否成功。 public static Mutex mutex = new Mutex(true, "mushroom.mutex", out createNew);static void Main(string[] args) { //======Example 5===== if (createNew) //第一个创建成功,这时候已经拿到锁了。 无需再WaitOne了。一定要注意。 { try { Run(); } finally { mutex.ReleaseMutex(); //释放当前锁。 } } //WaitOne 函数作用是阻止当前线程,直到拿到收到其他实例释放的处理信号。 //第一个参数是等待超时时间,第二个是否退出上下文同步域。 else if (mutex.WaitOne(10000,false))// { try { Run(); } finally { mutex.ReleaseMutex(); } } else//如果没有发现处理信号 { Console.WriteLine("已经有实例了。"); Console.ReadLine(); } } static void Run() { Console.WriteLine("实例1"); Console.ReadLine(); }
我们顺序起A B实例测试下。 A首先拿到锁,输出 实例1 。 B在等待, 如果10秒内A释放,B拿到执行Run()。 超时后输出 已经有实例了。
这里注意的是第一个拿到处理信号 的实例,已经拿到锁了。不需要再WaitOne。 否则报异常。
三:Semaphore
即信号量,我们可以把它理解为升级版的mutex。mutex对一个资源进行锁,semaphore则是对多个资源进行加锁。
semaphore是由windows内核维持一个int32变量的线程计数器,线程每调用一次、计数器减一、释放后对应加一, 超出的线程则排队等候。
走的是内核构造,所以semaphore也是可以跨进程的。
static void Main(string[] args) { Console.WriteLine("准备处理队列");bool createNew = false;
SemaphoreSecurity ss = new SemaphoreSecurity(); //信号量权限控制 Semaphore semaphore = new Semaphore(2, 2, "mushroom.Semaphore", out createNew,null); for (int i = 1; i <= 5; i++) { new Thread((arg) => { semaphore.WaitOne(); Console.WriteLine(arg + "处理中"); Thread.Sleep(10000); semaphore.Release(); //即semaphore.Release(1) //semaphore.Release(5);可以释放多个,但不能超过最大值。如果最后释放的总量超过本身总量,也会报错。 不建议使用
}).Start(i); } Console.ReadLine(); }
四:总结
mutex、Semaphore 需要由托管代码转成本地用户模式代码、再转换为本地内核代码。
反之同样,饶了一大圈,性能肯定不会很好。所以仅在需要跨进程的场景才使用。
本文向大家介绍C#多线程编程中的锁系统(三),包括了C#多线程编程中的锁系统(三)的使用技巧和注意事项,需要的朋友参考一下 本章主要说下基于内核模式构造的线程同步方式,事件,信号量。 目录 一:理论 二:WaitHandle 三:AutoResetEvent 四:ManualResetEvent 五:总结 一:理论 我们晓得线程同步可分为,用户模式构造和内核模式构造。 内核模式构造:是由windo
本文向大家介绍C#多线程编程中的锁系统(二),包括了C#多线程编程中的锁系统(二)的使用技巧和注意事项,需要的朋友参考一下 上章主要讲排他锁的直接使用方式。但实际当中全部都用锁又太浪费了,或者排他锁粒度太大了。 这一次我们说说升级锁和原子操作。 目录 1:volatile 2: Interlocked 3:ReaderWriterLockSlim 4:总结 一:volatile 简单来说: vo
本文向大家介绍C#多线程编程中的锁系统(四):自旋锁,包括了C#多线程编程中的锁系统(四):自旋锁的使用技巧和注意事项,需要的朋友参考一下 目录 一:基础 二:自旋锁示例 三:SpinLock 四:继续SpinLock 五:总结 一:基础 内核锁:基于内核对象构造的锁机制,就是通常说的内核构造模式。用户模式构造和内核模式构造 优点:cpu利用最大化。它发现资源被锁住,请求就排
问题内容: 我正在努力加快某些过程的执行速度,这些过程将大量记录(大多数是几百万个)发布到Elasticsearch。在我的C#代码中,我已经使用Dataflow实现了一个多线程解决方案,如下所示: 然后我要实现的发送批量请求调用: 我的问题 ,你 是对的实用性存在的数据流管道的一部分的锁内执行额外的线程。 这个可以吗?我可以在性能,执行,缓存/内存丢失等方面看到任何潜在的问题吗? 任何见识都会很
下面的代码应该从用户那里获取两个整数(每个输入一个线程),将它们相加(使用第三个线程)并打印总和。但是程序在第一个线程之后终止。 它给出的输出:
本文向大家介绍Python多线程编程(五):死锁的形成,包括了Python多线程编程(五):死锁的形成的使用技巧和注意事项,需要的朋友参考一下 前一篇文章Python:使用threading模块实现多线程编程四[使用Lock互斥锁]我们已经开始涉及到如何使用互斥锁来保护我们的公共资源了,现在考虑下面的情况– 如果有多个公共资源,在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待