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类似,但是它能跨进程锁资源(走的是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  需要由托管代码转成本地用户模式代码、再转换为本地内核代码。

反之同样,饶了一大圈,性能肯定不会很好。所以仅在需要跨进程的场景才使用。

时间: 2015-04-09

C#中lock死锁实例教程

在c#中有个关键字lock,它的作用是锁定某一代码块,让同一时间只有一个线程访问该代码块,本文就来谈谈lock关键字的原理和其中应注意的几个问题: lock的使用原型是: lock(X) { //需要锁定的代码.... } 首先要明白为什么上面这段话能够锁定代码,其中的奥妙就是X这个对象,事实上X是任意一种引用类型,它在这儿起的作用就是任何线程执行到lock(X)时候,X需要独享才能运行下面的代码,若假定现在有3个线程A,B,C都执行到了lock(X)而ABC因为此时都占有X,这时ABC就要停下

Windows中使用C#为文件夹和文件编写密码锁的示例分享

C#文件夹加锁小工具 用C#语言实现一个文件夹锁的程序,网上类似的"xxx文件夹xxx"软件很多,但是基本上都是C/C++语言实现的,且都没有提供源码(这个可以理解,毕竟是加密程序,不应该泄露源码). 程序的基本原理是:用C#语言重命名文件夹,通过重命名使之成为windows安全文件的类标识符.具体的方法是为文件夹添加拓展名".{2559a1f2-21d7-11d4-bdaf-00c04f60b9f0}" (.{2559a1f2-21d7-11d4-bdaf-00c

C#实现将程序锁定到Win7任务栏的方法

本文实例讲述了C#实现将程序锁定到Win7任务栏的方法.分享给大家供大家参考.具体实现方法如下: Win7Taskbar类: using System; using System.Collections.Generic; using System.Text; using Shell32; using System.IO; namespace TestWin7Taskbar { class Win7Taskbar { public static bool LockApp(bool isLock,

C#多线程编程中的锁系统(三)

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

如何使用C#读写锁ReaderWriterLockSlim

读写锁的概念很简单,允许多个线程同时获取读锁,但同一时间只允许一个线程获得写锁,因此也称作共享-独占锁.在C#中,推荐使用ReaderWriterLockSlim类来完成读写锁的功能. 某些场合下,对一个对象的读取次数远远大于修改次数,如果只是简单的用lock方式加锁,则会影响读取的效率.而如果采用读写锁,则多个线程可以同时读取该对象,只有等到对象被写入锁占用的时候,才会阻塞. 简单的说,当某个线程进入读取模式时,此时其他线程依然能进入读取模式,假设此时一个线程要进入写入模式,那么他不得不被阻塞

C#多线程中如何运用互斥锁Mutex

互斥锁(Mutex) 互斥锁是一个互斥的同步对象,意味着同一时间有且仅有一个线程可以获取它. 互斥锁可适用于一个共享资源每次只能被一个线程访问的情况 函数: //创建一个处于未获取状态的互斥锁 Public Mutex(): //如果owned为true,互斥锁的初始状态就是被主线程所获取,否则处于未获取状态 Public Mutex(bool owned): 如果要获取一个互斥锁.应调用互斥锁上的WaitOne()方法,该方法继承于Thread.WaitHandle类 它处于等到状态直至所调用

C#多线程编程中的锁系统(二)

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

C#检查键盘大小写锁定状态的方法

本文实例讲述了C#检查键盘大小写锁定状态的方法.分享给大家供大家参考.具体分析如下: 1.命名空间: using System.Runtime.InteropServices; 2.导入方法 [DllImport("user32.dll", EntryPoint = "GetKeyboardState")] public static extern int GetKeyboardState(byte[] pbKeyState); 3.大小写状态 public sta

C#解决SQlite并发异常问题的方法(使用读写锁)

本文实例讲述了C#解决SQlite并发异常问题的方法.分享给大家供大家参考,具体如下: 使用C#访问sqlite时,常会遇到多线程并发导致SQLITE数据库损坏的问题. SQLite是文件级别的数据库,其锁也是文件级别的:多个线程可以同时读,但是同时只能有一个线程写.Android提供了SqliteOpenHelper类,加入Java的锁机制以便调用.但在C#中未提供类似功能. 作者利用读写锁(ReaderWriterLock),达到了多线程安全访问的目标. using System; usin

C#多线程编程中的锁系统(四):自旋锁

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

Java多线程编程中synchronized关键字的基础用法讲解

多线程编程中,最关键.最关心的问题应该就是同步问题,这是一个难点,也是核心. 从jdk最早的版本的synchronized.volatile,到jdk 1.5中提供的java.util.concurrent.locks包中的Lock接口(实现有ReadLock,WriteLock,ReentrantLock),多线程的实现也是一步步走向成熟化.   同步,它是通过什么机制来控制的呢?第一反应就是锁,这个在学习操作系统与数据库的时候,应该都已经接触到了.在Java的多线程程序中,当多个程序竞争同一

详解Java多线程编程中互斥锁ReentrantLock类的用法

0.关于互斥锁 所谓互斥锁, 指的是一次最多只能有一个线程持有的锁. 在jdk1.5之前, 我们通常使用synchronized机制控制多个线程对共享资源的访问. 而现在, Lock提供了比synchronized机制更广泛的锁定操作, Lock和synchronized机制的主要区别: synchronized机制提供了对与每个对象相关的隐式监视器锁的访问, 并强制所有锁获取和释放均要出现在一个块结构中, 当获取了多个锁时, 它们必须以相反的顺序释放. synchronized机制对锁的释放是

详解Java多线程编程中的线程同步方法

1.多线程的同步: 1.1.同步机制: 在多线程中,可能有多个线程试图访问一个有限的资源,必须预防这种情况的发生.所以引入了同步机制:在线程使用一个资源时为其加锁,这样其他的线程便不能访问那个资源了,直到解锁后才可以访问. 1.2.共享成员变量的例子: 成员变量与局部变量: 成员变量: 如果一个变量是成员变量,那么多个线程对同一个对象的成员变量进行操作,这多个线程是共享一个成员变量的. 局部变量: 如果一个变量是局部变量,那么多个线程对同一个对象进行操作,每个线程都会有一个该局部变量的拷贝.他们

Java多线程编程中synchronized线程同步的教程

0.关于线程同步 (1)为什么需要同步多线程? 线程的同步是指让多个运行的线程在一起良好地协作,达到让多线程按要求合理地占用释放资源.我们采用Java中的同步代码块和同步方法达到这样的目的.比如这样的解决多线程无固定序执行的问题: public class TwoThreadTest { public static void main(String[] args) { Thread th1= new MyThread1(); Thread th2= new MyThread2(); th1.st

举例解析Java多线程编程中需要注意的一些关键点

1. 同步方法或同步代码块? 您可能偶尔会思考是否要同步化这个方法调用,还是只同步化该方法的线程安全子集.在这些情况下,知道 Java 编译器何时将源代码转化为字节代码会很有用,它处理同步方法和同步代码块的方式完全不同. 当 JVM 执行一个同步方法时,执行中的线程识别该方法的 method_info 结构是否有 ACC_SYNCHRONIZED 标记设置,然后它自动获取对象的锁,调用方法,最后释放锁.如果有异常发生,线程自动释放锁. 另一方面,同步化一个方法块会越过 JVM 对获取对象锁和异常

python多线程编程中的join函数使用心得

今天去辛集买箱包,下午挺晚才回来,又是恶心又是头痛.恶心是因为早上吃坏东西+晕车+回来时看到车祸现场,头痛大概是烈日和空调混合刺激而成.没有时间没有精神没有力气学习了,这篇博客就说说python中一个小小函数. 由于坑爹的学校坑爷的专业,多线程编程老师从来没教过,多线程的概念也是教的稀里糊涂,本人python也是菜鸟级别,所以遇到多线程的编程就傻眼了,别人用的顺手的join函数我却偏偏理解不来.早上在去辛集的路上想这个问题想到恶心,回来后继续写代码测试,终于有些理解了(python官方的英文解释