c#语言-多线程中的锁系统(一)

介绍

平常在多线程开发中,总避免不了线程同步。本篇就对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类似,是能跨进程锁的。 我们来看个例子

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内部一个线程计数器,线程每调用一次,计数器减一,释放后对应加一。 超出线程数量则排队等候。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  性能较差,需要跨进程的时候,再使用。

lock和Monitor    性能较好些。

注意死锁。

时间: 2024-08-05 05:58:31

c#语言-多线程中的锁系统(一)的相关文章

c#语言-多线程中的锁系统

介绍 平常在多线程开发中,总避免不了线程同步.这次就对net多线程中的锁系统做个简单描述. 目录 一:lock.Monitor 1:基础. 2: 作用域. 3:字符串锁. 二: mutex 三:Semaphore 四:总结 一:lock.Monitor 1:基础 Lock是Monitor语法糖简化写法.Lock在IL会生成Monitor. //======Example 1===== string obj = "helloworld"; lock (obj) { Console.Wri

多线程中的锁系统(一)-基础用法

目录 一: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会编译成如下写法 bo

多线程中的锁系统(二)-volatile、Interlocked、ReaderWriterLockSlim

介绍 上章主要说排他锁的直接使用方式.但实际当中全部都用锁又太浪费了,或者排他锁粒度太大了. 这一次我们说说升级锁和原子操作. 目录 1:volatile 2:  Interlocked 3:ReaderWriterLockSlim 4:总结 一:volatile 简单来说: volatile关键字是告诉c#编译器和JIT编译器,不对volatile标记的字段做任何的缓存.确保字段读写都是原子操作,最新值. 这不就是锁吗?   其这货它根本不是锁, 它的原子操作是基于CPU本身的,非阻塞的. 因

多线程中的锁系统(四)-谈谈自旋锁

目录 一:基础 二:自旋锁示例 三:SpinLock 四:继续SpinLock 五:总结 一:基础 内核锁:基于内核对象构造的锁机制,就是通常说的内核构造模式.用户模式构造和内核模式构造 优点:cpu利用最大化.它发现资源被锁住,请求就排队等候.线程切换到别处干活,直到接受到可用信号,线程再切回来继续处理请求. 缺点:托管代码->用户模式代码->内核代码损耗.线程上下文切换损耗. 在锁的时间比较短时,系统频繁忙于休眠.切换,是个很大的性能损耗. 自旋锁:原子操作+自循环.通常说的用户构造模式.

多线程中的锁系统(三)-WaitHandle、AutoResetEvent、ManualResetEvent

介绍 本章主要说下基于内核模式构造的线程同步方式,事件,信号量. 目录 一:理论 二:WaitHandle 三:AutoResetEvent 四:ManualResetEvent 五:总结 一:理论 我们晓得线程同步可分为,用户模式构造和内核模式构造. 内核模式构造:是由windows系统本身使用,内核对象进行调度协助的.内核对象是系统地址空间中的一个内存块,由系统创建维护. 内核对象为内核所拥有,而不为进程所拥有,所以不同进程可以访问同一个内核对象, 如进程,线程,作业,事件,文件,信号量,互

java 多线程中的锁的类别及使用

目前在Java中存在两种锁机制: synchronized Lock Lock接口及其实现类是JDK5增加的内容,其作者是大名鼎鼎的并发专家Doug Lea. 数据同步需要依赖锁,那锁的同步又依赖谁? synchronized给出的答案是在软件层面依赖JVM, 而Lock给出的方案是在硬件层面依赖特殊的CPU指令 Synchronized 其应用层的语义是可以把任何一个非null对象作为"锁" 当synchronized作用在方法上时,锁住的便是对象实例(this): 当作用在静态方法

java多线程中synchronize锁的使用和学习,Thread多线程学习(二)

synchronize我的理解是为了保证程序中的原子性和一致性,即当你有两个线程同时操作一段代码的时候,要让这段代码的执行是在任何状态下都是正确的,首先要保证synchronize的使用要对同一个对象和同一把锁使用. [java] view plain copy print? <span style="font-size:14px;">public class TraditionalThreadSynchronized { public static void main(S

多线程中的锁

乐观锁和悲观锁 乐观锁和悲观锁是在数据库中引入的名词,但是在并发包锁里面也引入了类似的思想,所以这里还是有必要讲解一下. 悲观锁指对数据被外界修改持保守的态度,认为数据很容易就会被其他线程修改,所以在数据被处理前就先对数据加锁,并在整个数据处理过程中,使数据处于锁定状态,悲观锁的实现往往依靠数据库提供的锁机制,即在数据库中,在对数据记录操作前给记录加排它锁,如果获取锁失败,则说明数据正在被其他线程修改,当前线程则等待或者抛出异常,如果获取锁成功,则对记录进行操作,然后提交事务后释放排它锁 乐观锁

多线程中的锁的几种用法总结

一.ReentrantLock 1 package com.ietree.basicskill.mutilthread.lock; 2 3 import java.util.concurrent.locks.Lock; 4 import java.util.concurrent.locks.ReentrantLock; 5 6 /** 7 * Created by Administrator on 2017/5/17. 8 */ 9 public class UseReentrantLock {