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


上章主要讲排他锁的直接使用方式。但实际当中全部都用锁又太浪费了,或者排他锁粒度太大了。 这一次我们说说升级锁和原子操作。

目录
1:volatile
2:  Interlocked
3:ReaderWriterLockSlim
4:总结

一:volatile

简单来说: volatile关键字是告诉c#编译器和JIT编译器,不对volatile标记的字段做任何的缓存。确保字段读写都是原子操作,最新值。

这不就是锁吗?   其这货它根本不是锁, 它的原子操作是基于CPU本身的,非阻塞的。 因为32位CPU执行赋值指令,数据传输最大宽度4个字节。

所以只要在4个字节以下读写操作的,32位CPU都是原子操作。volatile 它就是利用这个特性来的。

好残酷的事实?不然,微软大法这样是为了提高JIT性能效率,对有些数据进行缓存了(多线程下)。

复制代码 代码如下:
  //正确
       public volatile Int32 score1 = 1;
        //报错
        public volatile Int64 score2 = 1;

看上面的例子,我们定义8个字节长度score2就不行了。  因为8个字节,32位CPU就分成2个指令执行了。自然就无法保证原子操作了。

这么细节的,忘了怎么办,那岂不是坑人啊。  于是微软大法直接一棍子打死,限制4个字节以下的类型字段才能用volatile,具体什么、看msdn吧。

那今天我知道了。我编译平台改成64位上,只在64位CPU用volatile  int64,行不行?  不行,编译器报错。说了一棍子打死了。。

(^._.^)ノ  好吧,其实可以用IntPtr这个。

volatile多数情况下很有用处的,毕竟锁的性能开销还是很大的。我们可以把当成轻量级的锁,根据具体场景合理使用,能提高不少程序性能。

线程中的Thread.VolatileRead 和Thread.VolatileWrite 就是volatile的复杂版。

二:Interlocked

MSDN 描述:为多个线程共享的变量提供原子操作。主要函数如下:

Interlocked.Increment    原子操作,递增指定变量的值并存储结果。
Interlocked.Decrement       原子操作,递减指定变量的值并存储结果。
Interlocked.Add        原子操作,添加两个整数并用两者的和替换第一个整数

Interlocked.CompareExchange(ref a, b, c);  原子操作,a参数和c参数比较,  相等b替换a,不相等不替换。

基本用法就不多说了。直接来段CLR via C# interlock anything的例子:

复制代码 代码如下:
public static int Maximum(ref int target, int value)
        {
            int currentVal = target, startVal, desiredVal;  //记录前后值
            do
            {
                startVal = currentVal; //记录循环迭代的初始值。
                desiredVal = Math.Max(startVal, value); //基于startVal和value计算期望值desiredVal

//高并发下,线程被抢占情况下,target值会发生改变。

//target startVal相等说明没改变。desiredVal 直接替换。
                currentVal = Interlocked.CompareExchange(ref target, desiredVal, startVal);

} while (startVal != currentVal); //不相等说明,target值已经被其他线程改动。自旋继续。
            return desiredVal;
        }

三:ReaderWriterLockSlim

假如我们有份缓存数据A,如果每次都不管任何操作lock一下,那么我的这份缓存A就永远只能单线程读写了, 这在Web高并发下是不能忍受的。

那有没有一种办法我只在写入时进入独占锁呢,读操作时不限制线程数量呢?答案就是我们的ReaderWriterLockSlim主角,读写锁。

ReaderWriterLockSlim 其中一种锁EnterUpgradeableReadLock最关键  即可升级锁。

它呢允许你先进入读锁,发现缓存A不一样了, 再进入写锁,写入后退回读锁模式。

ps: 这里注意下net 3.5之前有个ReaderWriterLock 性能较差。推荐使用升级版的 ReaderWriterLockSlim 。

复制代码 代码如下:
//实例一个读写锁
 ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);

上面实例一个读写锁,这里注意的是构造函数的枚举。

LockRecursionPolicy.NoRecursion 不支持,发现递归会抛异常。

LockRecursionPolicy.SupportsRecursion  即支持递归模式,线程锁中继续在使用锁。

复制代码 代码如下:
cacheLock.EnterReadLock();
            //do
                cacheLock.EnterReadLock();
                //do
                cacheLock.ExitReadLock();
            cacheLock.ExitReadLock();

这种模式极易容易死锁,比如读锁里面使用写锁。

复制代码 代码如下:
cacheLock.EnterReadLock();
            //do
              cacheLock.EnterWriteLock();
              //do
              cacheLock.ExitWriteLock();
            cacheLock.ExitReadLock();

下面是直接拿msdn的缓存例子了,加了简单注释。

复制代码 代码如下:
public class SynchronizedCache
    {
        private ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim();
        private Dictionary<int, string> innerCache = new Dictionary<int, string>();

public string Read(int key)
        {
            //进入读锁,允许其他所有的读线程,写入线程被阻塞。
            cacheLock.EnterReadLock();
            try
            {
                return innerCache[key];
            }
            finally
            {
                cacheLock.ExitReadLock();
            }
        }

public void Add(int key, string value)
        {
            //进入写锁,其他所有访问操作的线程都被阻塞。即写独占锁。
            cacheLock.EnterWriteLock();
            try
            {
                innerCache.Add(key, value);
            }
            finally
            {
                cacheLock.ExitWriteLock();
            }
        }

public bool AddWithTimeout(int key, string value, int timeout)
        {
            //超时设置,如果在超时时间内,其他写锁还不释放,就放弃操作。
            if (cacheLock.TryEnterWriteLock(timeout))
            {
                try
                {
                    innerCache.Add(key, value);
                }
                finally
                {
                    cacheLock.ExitWriteLock();
                }
                return true;
            }
            else
            {
                return false;
            }
        }

public AddOrUpdateStatus AddOrUpdate(int key, string value)
        {
            //进入升级锁。 同时只能有一个可升级锁线程。写锁,升级锁都被阻塞,但允许其他读取数据的线程。
            cacheLock.EnterUpgradeableReadLock();
            try
            {
                string result = null;
                if (innerCache.TryGetValue(key, out result))
                {
                    if (result == value)
                    {
                        return AddOrUpdateStatus.Unchanged;
                    }
                    else
                    {
                        //升级成写锁,其他所有线程都被阻塞。
                        cacheLock.EnterWriteLock();
                        try
                        {
                            innerCache[key] = value;
                        }
                        finally
                        {
                            //退出写锁,允许其他读线程。
                            cacheLock.ExitWriteLock();
                        }
                        return AddOrUpdateStatus.Updated;
                    }
                }
                else
                {
                    cacheLock.EnterWriteLock();
                    try
                    {
                        innerCache.Add(key, value);
                    }
                    finally
                    {
                        cacheLock.ExitWriteLock();
                    }
                    return AddOrUpdateStatus.Added;
                }
            }
            finally
            {
                //退出升级锁。
                cacheLock.ExitUpgradeableReadLock();
            }
        }

public enum AddOrUpdateStatus
        {
            Added,
            Updated,
            Unchanged
        };
    }

四:总结

多线程实际开发当中,往往测试没问题,一到生产环境,并发高了就容易出问题, 一定注意。

本文参考CLR via C#。

 

出处:https://www.xp.cn/b.php/60358.html

C#多线程编程中的锁系统的更多相关文章

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

    介绍 平常在多线程开发中,总避免不了线程同步.本篇就对net多线程中的锁系统做个简单描述.   目录 一:lock.Monitor        1:基础.        2: 作用域.       ...

  2. Java多线程编程(2)--多线程编程中的挑战

    一.串行.并发和并行   为了更清楚地解释这三个概念,我们来举一个例子.假设我们有A.B.C三项工作要做,那么我们有以下三种方式来完成这些工作:   第一种方式,先开始做工作A,完成之后再开始做工作B ...

  3. 关于python多线程编程中join()和setDaemon()的一点儿探究

    关于python多线程编程中join()和setDaemon()的用法,这两天我看网上的资料看得头晕脑涨也没看懂,干脆就做一个实验来看看吧. 首先是编写实验的基础代码,创建一个名为MyThread的  ...

  4. Java多线程编程中Future模式的详解

    Java多线程编程中,常用的多线程设计模式包括:Future模式.Master-Worker模式.Guarded Suspeionsion模式.不变模式和生产者-消费者模式等.这篇文章主要讲述Futu ...

  5. C++ 关于MFC多线程编程中的一些注意事项 及自定义消息的处理

    在多线程编程中,最简单的方法,无非就是利用 AfxBeginThread  来创建一个工作线程,看一下这个函数的说明: CWinThread* AFXAPI AfxBeginThread( AFX_T ...

  6. Java多线程编程中Future模式的详解<转>

    Java多线程编程中,常用的多线程设计模式包括:Future模式.Master-Worker模式.Guarded Suspeionsion模式.不变模式和生产者-消费者模式等.这篇文章主要讲述Futu ...

  7. Qt多线程编程中的对象线程与函数执行线程

    近来用Qt编写一段多线程的TcpSocket通信程序,被其中Qt中报的几个warning搞晕了,一会儿是说“Cannot create children for a parent that is in ...

  8. 【C/C++开发】多线程编程中的join函数

    多线程编程中的join函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 # coding: utf-8 # 测试多线程中join的 ...

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

    本章主要介绍下基于内核模式构造的线程同步方式,事件,信号量. 阅读目录: 理论 WaitHandle AutoResetEvent ManualResetEvent 总结 理论 Windows的线程同 ...

随机推荐

  1. Kubernetes之Taints与Tolerations 污点和容忍

    NodeAffinity节点亲和性,是Pod上定义的一种属性,使Pod能够按我们的要求调度到某个Node上,而Taints则恰恰相反,它可以让Node拒绝运行Pod,甚至驱逐Pod. Taints(污 ...

  2. VUE:页面跳转时传递参数,及参数获取

    https://www.cnblogs.com/zhongchao666/p/9679807.html https://blog.csdn.net/mf_717714/article/details/ ...

  3. 【Activiti学习之二】Activiti API(一)

    环境 JDK 1.8 MySQL 5.6 Tomcat 7 Eclipse-Luna activiti 6.0 一.Activiti数据查询准备数据: package com.wjy.act; imp ...

  4. 修改mysql存储过程或函数的定义着

    以root用户登录mysql控制台 (1)首先查询 mysql> select definer from mysql.proc; (2)然后根据条件进行更新 update mysql.proc ...

  5. 使用 KVO 可能会拖慢启动速度

    问题  在某一次启动速度优化中,发现最开始的某个 runLoop 中,一个runLoop 耗时很长.发现一个 KVO 变量的初始化消耗了13ms之久,这对启动速度是不可接受了. 源码分析 用 Ins ...

  6. linux_problem

    今日自学遇到两个问题:火狐浏览器显示安全错误,按照国内网站上抄来抄去的解决办法并没有解决我的问题,即,每次访问新的网站都会提示"support mozilla.org 的管理员...&quo ...

  7. scaffold

    #!/usr/bin/env python # -*- coding: utf-8 -*- from __future__ import print_function import argparse ...

  8. Linux VIM8.1 Python3 编辑器配置文件

    Linux VIM8.1 Python3 编辑器配置文件 实现功能: 自动补全(包括函数模块方法补全) 自动代码标准格式化 自动检查代码错误 自定义头文件 自动括号补全 缩进指示线 代码一键折叠 代码 ...

  9. linux 1-常用命令

    文件处理命令: 命令格式:命令 [-选项] [参数] 例如:ls -la /etc   多个选项可以写在一起,不区分前后关系,例如 -l 和 -a 一起写成 -la 目录处理命令:ls (就是list ...

  10. 给定一个长度为N的数组,找出出现次数大于n/2,n/3的数,要求时间复杂度O(n),空间复杂度O(1)

    先讨论出现次数大于n/2的数字,如果这样的数字存在,那么这个数出现的次数大于其他数出现的次数的总和. 在数组A中,我们定义两个数据集合a1,a2.a1为出现次数大于n/2的数的集合,a2为其余数组成的 ...