开篇:

互斥还是lock Monitor Mutex 模式!

Muex Monitor lock AutoEventSet ManualEventSet

后续的

ReaderWriterLock

ReaderWriterLockSlim 类

表示用于管理资源访问的锁定状态,可实现多线程读取或进行独占式写入访问。

使用 ReaderWriterLockSlim 来保护由多个线程读取但每次只采用一个线程写入的资源。 ReaderWriterLockSlim 允许多个线程均处于读取模式,允许一个线程处于写入模式并独占锁定状态,同时还允许一个具有读取权限的线程处于可升级的读取模式,在此模式下线程无需放弃对资源的读取权限即可升级为写入模式。

注意 ReaderWriterLockSlim 类似于 ReaderWriterLock,只是简化了递归、升级和降级锁定状态的规则。 ReaderWriterLockSlim 可避免多种潜在的死锁情况。 此外,ReaderWriterLockSlim 的性能明显优于 ReaderWriterLock。 建议在所有新的开发工作中使用 ReaderWriterLockSlim。

以上引用自MSDN

C# 线程手册 第三章 使用线程 ReaderWriterLock 类

2012-02-07 21:53 by DanielWise, 4166 阅读, 1 评论, 收藏, 编辑

一个ReaderWriterLock 类定义一个实现单写多读语义的锁。这个类通常用在能被多个线程读取但是仅能被一个线程写入的文件操作时使用。下面是ReaderWriterLock类中的四个主要方法:

a. AcquireReaderLock(): 这个重载方法获取一个读者锁,接受一个整型或者TimeSpan类型的timeout 值。timeout是一个检测死锁的利器。

b. AcquireWriterLock():  这个重载方法获取一个写者锁,接受一个整型或者TimeSpan类型的timeout 值。

c. ReleaseReaderLock(): 释放读者锁。

d. ReleaseWriterLock(): 释放写者锁。

使用ReaderWriterLock类可以让多线程安全地进行数据并发读取。只有当线程正在更新的数据锁定。读者线程可以再没有写者拥有锁的时候获得锁。写者线程可以再没有读者线程或者写者线程拥有锁的时候获得锁。

下面的列表ReadeWriteLock.cs, 描述了如何使用ReaderWriterLock()锁:

/*************************************
/* copyright (c) 2012 daniel dong* * author:daniel dong* blog:  www.cnblogs.com/danielwise* email: guofoo@163.com* */using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;namespace ReadWriteLock
{public class ReadWrite{private ReaderWriterLock rwl;private int x;private int y;public ReadWrite(){rwl = new ReaderWriterLock();}public void ReadInts(ref int a, ref int b){rwl.AcquireReaderLock(Timeout.Infinite);try{a = this.x;b = this.y;}finally{rwl.ReleaseReaderLock();}}public void WriteInts(int a, int b){rwl.AcquireWriterLock(Timeout.Infinite);try{this.x = a;this.y = b;Console.WriteLine("x = " + this.x+ " y = " + this.y+ " ThreadID = " + Thread.CurrentThread.GetHashCode());}finally{rwl.ReleaseWriterLock();}}}public class RWApp{private ReadWrite rw = new ReadWrite();public static void Main(string[] args){RWApp e = new RWApp();//Writer ThreadsThread wt1 = new Thread(new ThreadStart(e.Write));wt1.Start();Thread wt2 = new Thread(new ThreadStart(e.Write));wt2.Start();//Reader ThreadsThread rt1 = new Thread(new ThreadStart(e.Read));rt1.Start();Thread rt2 = new Thread(new ThreadStart(e.Read));rt2.Start();Console.ReadLine();}private void Write(){int a = 10;int b = 11;Console.WriteLine("************** Write *************");for (int i = 0; i < 5; i++){this.rw.WriteInts(a++, b++);Thread.Sleep(1000);}}private void Read(){int a = 10;int b = 11;Console.WriteLine("************** Read *************");for (int i = 0; i < 5; i++){this.rw.ReadInts(ref a, ref b);Console.WriteLine("For i = " + i+ " a = " + a+ " b = " + b+ " TheadID = " + Thread.CurrentThread.GetHashCode());Thread.Sleep(1000);}}}
}

ReadWriteLock 的输出结果可能与下表类似:

在上面的列表中,线程wt1 和 wt2 是WriteInts()方法中获得写锁的写者线程,线程rt1 和 rt2 是在ReadInts()方法中获得读者锁的读者线程。在WriteInts()方法中,变量x 和 y 的值分别被改成a 和 b. 当线程wt1 或 wt2 通过调用AcquireWriterLock() 方法获得一个写者锁后,那么直到这个线程通过调用ReleaseWriterLock()方法释放锁之前任何其他线程(包括读者线程rt1 和 rt2)都不被允许访问相应对象。这个行为与Monitors类似 。在ReadInts()方法中,线程rt1 和 rt2 通过调用AcquireReaderLock()方法获得读者锁, 这两个线程可以并发地访问变量x 和 y. 直到读者线程释放它们的读者锁以后,写者线程(wt1 和 wt2)才被允许访问对应对象。只有读者线程在获得读者锁以后才可以并发地访问。

Monitors类对于只想来读数据而非写数据来说过于“安全”了。Monitors 也有一些性能问题,对于只读类型的访问来说,性能瓶颈是可以避免的。ReaderWriterLock类通过允许任意数量的线程并发地读取数据来提供一个解决数据读-写问题的完美方案。当线程更新数据时锁住数据。当没有写者线程拥有锁的时候写者线程可以获得锁。写者锁可以在没有读者线程或者写者线程拥有锁的时候获得锁。因此,ReaderWriterLock 就像是一段关键部分代码, 它也支持一个timeout 值,而这方面在检测死锁时非常有用。

1.几种同步方法的区别

lock和Monitor是.NET用一个特殊结构实现的,Monitor对象是完全托管的、完全可移植的,并且在操作系统资源要求方 面可能更为有效,同步速度较快,但不能跨进程同步。lock(Monitor.Enter和Monitor.Exit方法的封装),主要作用是锁定临界区,使临 界区代码只能被获得锁的线程执行。Monitor.Wait和Monitor.Pulse用于线程同步,类似信号操作,个人感觉使用比较复杂,容易造成死 锁。

互斥体Mutex和事件对象EventWaitHandler属于内核对象,利用内核对象进行线程同步,线程必须要在用户模式和内核模 式间切换,所以一般效率很低,但利用互斥对象和事件对象这样的内核对象,可以在多个进程中的各个线程间进行同步。

互斥体Mutex类似于一个接力棒,拿到接力棒的线程才可以开始跑,当然接力棒一次只属于一个线程(Thread Affinity),如果这个线程不释放接力棒(Mutex.ReleaseMutex),那么没办法,其他所有需要接力棒运行的线程都知道能等着看热 闹。

EventWaitHandle 类允许线程通过发信号互相通信。 通常,一个或多个线程在 EventWaitHandle 上阻止,直到一个未阻止的线程调用 Set 方法,以释放一个或多个被阻止的线程。

2.什么时候需要锁定

首先要理解锁定是解决竞争条件的,也就是多个线程同时访问某个资源,造成意想不到的结果。比如,最简单的情况是,一个计数器,两个线程 同时加一,后果就是损失了一个计数,但相当频繁的锁定又可能带来性能上的消耗,还有最可怕的情况死锁。那么什么情况下我们需要使用锁,什么情况下不需要 呢?

1)只有共享资源才需要锁定
      只有可以被多线程访问的共享资源才需要考虑锁定,比如静态变量,再比如某些缓存中的值,而属于线程内部的变量不需要锁定。

2)多使用lock,少用Mutex
      如果你一定要使用锁定,请尽量不要使用内核模块的锁定机制,比如.NET的Mutex,Semaphore,AutoResetEvent和 ManuResetEvent,使用这样的机制涉及到了系统在用户模式和内核模式间的切换,性能差很多,但是他们的优点是可以跨进程同步线程,所以应该清 楚的了解到他们的不同和适用范围。

3)了解你的程序是怎么运行的
      实际上在web开发中大多数逻辑都是在单个线程中展开的,一个请求都会在一个单独的线程中处理,其中的大部分变量都是属于这个线程的,根本没有必要考虑锁 定,当然对于ASP.NET中的Application对象中的数据,我们就要考虑加锁了。

4)把锁定交给数据库
      数 据库除了存储数据之外,还有一个重要的用途就是同步,数据库本身用了一套复杂的机制来保证数据的可靠和一致性,这就为我们节省了很多的精力。保证了数据源 头上的同步,我们多数的精力就可以集中在缓存等其他一些资源的同步访问上了。通常,只有涉及到多个线程修改数据库中同一条记录时,我们才考虑加锁。

5)业务逻辑对事务和线程安全的要求
      这 条是最根本的东西,开发完全线程安全的程序是件很费时费力的事情,在电子商务等涉及金融系统的案例中,许多逻辑都必须严格的线程安全,所以我们不得不牺牲 一些性能,和很多的开发时间来做这方面的工作。而一般的应用中,许多情况下虽然程序有竞争的危险,我们还是可以不使用锁定,比如有的时候计数器少一多一, 对结果无伤大雅的情况下,我们就可以不用去管它。

3.InterLocked类

Interlocked 类提供了同步对多个线程共享的变量的访问的方法。如果该变量位于共享内存中,则不同进程的线程就可以使用该机制。互锁操作是原子的,即整个操作是不能由相 同变量上的另一个互锁操作所中断的单元。这在抢先多线程操作系统中是很重要的,在这样的操作系统中,线程可以在从某个内存地址加载值之后但是在有机会更改 和存储该值之前被挂起。

我们来看一个InterLock.Increment()的例子,该方法以原子的形式递增指定变量并存储结果,示例如下:


    class InterLockedTest
    {
        public static Int64 i = 0;

public static void Add()
        {
            for (int i = 0; i < 100000000; i++)
            {
                Interlocked.Increment(ref InterLockedTest.i);
                //InterLockedTest.i = InterLockedTest.i + 1;
            }
        }

public static void Main(string[] args)
        {
            Thread t1 = new Thread(new ThreadStart(InterLockedTest.Add));
            Thread t2 = new Thread(new ThreadStart(InterLockedTest.Add));

t1.Start();
            t2.Start();

t1.Join();
            t2.Join();

Console.WriteLine(InterLockedTest.i.ToString());
            Console.Read();
        }
    }

输出结果200000000,如果InterLockedTest.Add()方法中用注释掉的语句代替Interlocked.Increment() 方法,结果将不可预知,每次执行结果不同。InterLockedTest.Add()方法保证了加1操作的原子性,功能上相当于自动给加操作使用了 lock锁。同时我们也注意到InterLockedTest.Add()用时比直接用+号加1要耗时的多,所以说加锁资源损耗还是很明显的。

另外InterLockedTest类还有几个常用方法,具体用法可以参考MSDN上的介绍。

4.集合类的同步

.NET在一些集合类,比如Queue、ArrayList、HashTable和Stack,已经提供了一个供lock使用的对象SyncRoot。用 Reflector查看了SyncRoot属性(Stack.SynchRoot略有不同)的源码如下:


public virtual object SyncRoot
{
    get
    {
        if (this._syncRoot == null)
        {
            //如果_syncRoot和null相等,将new object赋值给 _syncRoot
            //Interlocked.CompareExchange方法保证多个线程在使用 syncRoot时是线程安全的
            Interlocked.CompareExchange(ref this._syncRoot, new object(), null);
        }
        return this._syncRoot;
    }
}

这里要特别注意的是MSDN提到:从头到尾对一个集合进行枚举本质上并不是一个线程安全的过程。即使一个集合已进行同步,其他线程仍可以修改该集合,这将 导致枚举数引发异常。若要在枚举过程中保证线程安全,可以在整个枚举过程中锁定集合,或者捕捉由于其他线程进行的更改而引发的异常。应该使用下面的代码:

Queue使用lock示例

还有一点需要说明的是,集合类提供了一个是和同步相关的方法Synchronized,该 方法返回一个对应的集合类的wrapper类,该类是线程安全的,因为他的大部分方法都用lock关键字进行了同步处理。如HashTable的 Synchronized返回一个新的线程安全的HashTable实例,代码如下:


    //在多线程环境中只要我们用下面的 方式实例化HashTable就可以了
    Hashtable ht = Hashtable.Synchronized(new Hashtable());

//以下代码是.NET Framework Class Library实现,增加对 Synchronized的认识
    [HostProtection(SecurityAction.LinkDemand, Synchronization=true)]
    public static Hashtable Synchronized(Hashtable table)
    {
        if (table == null)
        {
            throw new ArgumentNullException("table");
        }
        return new SyncHashtable(table);
    }

//SyncHashtable的几个常用方法,我们可以看到内部实现都加了lock关键字 保证线程安全
    public override void Add(object key, object value)
    {
        lock (this._table.SyncRoot)
        {
            this._table.Add(key, value);
        }
    }

public override void Clear()
    {
        lock (this._table.SyncRoot)
        {
            this._table.Clear();
        }
    }

public override void Remove(object key)
    {
        lock (this._table.SyncRoot)
        {
            this._table.Remove(key);
        }
    }

线程同步是一个非常复杂的话题,这里只是根据公司的一个项目把相关的知识整理出来,作为工作的一种总结。这些同步方法的使用场景是怎样的?究竟有哪些细微 的差别?还有待于进一步的学习和实践。

C# 多线程并发锁模式-总结相关推荐

  1. Java 多线程 并发 锁 Java线程面试题

    1) 什么是线程? 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位.程序员可以通过它进行多处理器编程,你可以使用多线程对运算密集型任务提速.比如,如果一个线程完成 ...

  2. Java多线程| 并发| 锁 深入学习

    主要内容 左转Java内存模型 右转AbstractQueuedSynchronizer 分析 后转StampedLock分析 * 线程概念 * 特性.上下文切换.线程状态 * 线程控制 * Thre ...

  3. Java并发相关知识(多线程、锁、容器、工具)

    目录 一.基础知识 线程之间如何通信? Java内存模型 内存屏障 顺序一致性 CAS实现原理 原子操作 volatile synchronized 实现原理 什么是锁 原子操作类说明 高性能原子类 ...

  4. 《Java高并发核心编程.卷2,多线程、锁、JMM、JUC、高并发设计模式》

    <Java高并发核心编程.卷2,多线程.锁.JMM.JUC.高并发设计模式> 目录 第1章 多线程原理与实战 1.2 无处不在的进程和线程 1.2.1 进程的基本原理 1.2.2 线程的基 ...

  5. java线程钥匙_Java多线程并发编程/锁的理解

    一.前言 最近项目遇到多线程并发的情景(并发抢单&恢复库存并行),代码在正常情况下运行没有什么问题,在高并发压测下会出现:库存超发/总库存与sku库存对不上等各种问题. 在运用了 限流/加锁等 ...

  6. python 多线程并发编程(生产者、消费者模式),边读图像,边处理图像,处理完后保存图像实现提高处理效率

    文章目录 需求 实现 先导入本次需要用到的包 一些辅助函数 如下函数是得到指定后缀的文件 如下的函数一个是读图像,一个是把RGB转成BGR 下面是主要的几个处理函数 在上面几个函数构建对应的处理函数 ...

  7. python paramiko并发_python学习笔记9--paramiko模块、多线程、锁机制

    一.paramiko模块 paramiko模块是一个遵循ssh2协议的python扩展模块,该模块可以允许使用python通过ssh协议去远程管理主机.在使用该模块前,需要手动安装,具体安装过程请百度 ...

  8. 多线程并发安全问题与线程锁

    一.多线程并发安全问题 二.什么是线程锁及分类 三.synchronized关键字 多线程并发安全问题 当多个线程并发操作同一临界资源,由于线程切换时机不确定,导致操作临界资源的顺序出现混乱严重时可能 ...

  9. java并发编程(二十六)——单例模式的双重检查锁模式为什么必须加 volatile?

    前言 本文我们从一个问题出发来进行探究关于volatile的应用. 问题:单例模式的双重检查锁模式为什么必须加 volatile? 什么是单例模式 单例模式指的是,保证一个类只有一个实例,并且提供一个 ...

最新文章

  1. 64位手机部署centos
  2. 2014恒生电子实习生笔试数据库部分
  3. what to improve on my case study?
  4. Vsftp与PAM虚拟用户
  5. C++内存分配方式详解——堆、栈、自由存储区、全局/静态存储区和常量存储区...
  6. APUE 12.7 取消选项
  7. sp_help用法_sp_updatestats概述和用法
  8. 开始做我的robot博客
  9. 电商推荐系统论文:基于Spark机器学习的电商推荐系统的设计与实现,大数据电商推荐系统毕设论文,Spring MLlib电商推荐系统
  10. Unity app 如何打开商店
  11. maven命令创建支持eclipse的多模块maven项目
  12. fiddler抓手机显示网络连接失败
  13. 打开 cmd 的方式
  14. 「津津乐道播客」#282 科技乱炖:被电子发票干掉的顺丰?
  15. 现在的钱越来越难挣了吗?
  16. 对云计算,大数据和人工智能的浅谈(三)
  17. 咸鱼ZTMS实例—三轴加速传感器
  18. PMP笔记:质量管理的七个工具
  19. 金蝶EAS补丁部署操作步骤
  20. 画论69 汪之元《天下有山堂画艺》

热门文章

  1. 台湾积体电路制造公司(简称为台积电(TSMC))的28nm LP、HPM、HPC、HPC+四种不同处理器工艺版本的区别?
  2. 7.STM32中对DMA_Config()函数的理解(自定义)测试DMA传输数据时CPU还可继续工作其他的事
  3. Java 详解 JVM 工作原理和流程
  4. Excel基础操作(五)--图表基础
  5. NOIP2016提高组复赛解题报告
  6. Android UI(继承控件)--PopupWindow设置动画
  7. 哈希表的实现(取余法)
  8. 从Struts2 action 获取json 数据格式 显示到Jquery EasyUI
  9. varnish 防盗链
  10. AVPlayer支持VSFilter啦, 也就支持字幕啦