多线程下解决资源竞争的7种方法

前言

  一般情况下,只要涉及到多线程编程,程序的复杂性就会显著上升,性能显著下降,BUG出现的概率大大提升。

多线程编程本意是将一段程序并行运行,提升数据处理能力,但是由于大部分情况下都涉及到共有资源的竞争,所以修改资源

对象时必须加锁处理。但是锁的实现有很多种方法,下面就来一起了解一下在C#语言中几种锁的实现与其性能表现。

一、c#下的几种锁的运用方式

1、临界区,通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。

 1         private static object obj = new object();
 2         private static int lockInt;
 3         private static void LockIntAdd()
 4         {
 5             for (var i = 0; i < runTimes; i++)
 6             {
 7                 lock (obj)
 8                 {
 9                     lockInt++;
10                 }
11             }
12         }

你没看错,c#中的lock语法就是临界区(Monitor)的一个语法糖,这大概是90%以上的.net程序员首先想到的锁,不过大部分人都只是知道

有这么个语法,不知道其实是以临界区的方式处理资源竞争。

2、互斥量,为协调共同对一个共享资源的单独访问而设计的。

c#中有一个Mutex类,就在System.Threading命名空间下,Mutex其实就是互斥量,互斥量不单单能处理多线程之间的资源竞争,还能处理

进程之间的资源竞争,功能是比较强大的,但是开销也很大,性能比较低。

 1         private static Mutex mutex = new Mutex();
 2         private static int mutexInt;
 3         private static void MutexIntAdd()
 4         {
 5             for (var i = 0; i < runTimes; i++)
 6             {
 7                 mutex.WaitOne();
 8                 mutexInt++;
 9                 mutex.ReleaseMutex();
10             }
11         }

3、信号量,为控制一个具有有限数量用户资源而设计。

 1         private static Semaphore sema = new Semaphore(1, 1);
 2         private static int semaphoreInt;
 3         private static void SemaphoreIntAdd()
 4         {
 5             for (var i = 0; i < runTimes; i++)
 6             {
 7                 sema.WaitOne();
 8                 semaphoreInt++;
 9                 sema.Release();
10             }
11         }

4、事   件:用来通知线程有一些事件已发生,从而启动后继任务的开始。

 1         public static AutoResetEvent autoResetEvent = new AutoResetEvent(true);
 2         private static int autoResetEventInt;
 3         private static void AutoResetEventIntAdd()
 4         {
 5             for (var i = 0; i < runTimes; i++)
 6             {
 7                 if (autoResetEvent.WaitOne())
 8                 {
 9                     autoResetEventInt++;
10                     autoResetEvent.Set();
11                 }
12             }
13         }

5、读写锁,这种锁允许在有其他程序正在写的情况下读取资源,所以如果资源允许脏读,用这个比较合适

 1         private static ReaderWriterLockSlim LockSlim = new ReaderWriterLockSlim();
 2         private static int lockSlimInt;
 3         private static void LockSlimIntAdd()
 4         {
 5             for (var i = 0; i < runTimes; i++)
 6             {
 7                 LockSlim.EnterWriteLock();
 8                 lockSlimInt++;
 9                 LockSlim.ExitWriteLock();
10             }
11         }

6、原子锁,通过原子操作Interlocked.CompareExchange实现“无锁”竞争

 1         private static int isLock;
 2         private static int ceInt;
 3         private static void CEIntAdd()
 4         {
 5             //long tmp = 0;
 6             for (var i = 0; i < runTimes; i++)
 7             {
 8                 while (Interlocked.CompareExchange(ref isLock, 1, 0) == 1) { Thread.Sleep(1); }
 9
10                 ceInt++;
11                 Interlocked.Exchange(ref isLock, 0);
12             }
13         }

7、原子性操作,这是一种特例,野外原子性操作本身天生线程安全,所以无需加锁

1         private static int atomicInt;
2         private static void AtomicIntAdd()
3         {
4             for (var i = 0; i < runTimes; i++)
5             {
6                 Interlocked.Increment(ref atomicInt);
7             }
8         }

8、不加锁,如果不加锁,那多线程下运行结果肯定是错的,这里贴上来比较一下性能

1         private static int noLockInt;
2         private static void NoLockIntAdd()
3         {
4             for (var i = 0; i < runTimes; i++)
5             {
6                 noLockInt++;
7             }
8         }

二、性能测试

1、测试代码,执行1000,10000,100000,1000000次

 1         private static void Run()
 2         {
 3             var stopwatch = new Stopwatch();
 4             var taskList = new Task[loopTimes];
 5
 6             // 多线程
 7             Console.WriteLine();
 8             Console.WriteLine($"              线程数:{loopTimes}");
 9             Console.WriteLine($"            执行次数:{runTimes}");
10             Console.WriteLine($"        校验值应等于:{runTimes * loopTimes}");
11
12             // AtomicIntAdd
13             stopwatch.Restart();
14             for (var i = 0; i < loopTimes; i++)
15             {
16                 taskList[i] = Task.Factory.StartNew(() => { AtomicIntAdd(); });
17             }
18             Task.WaitAll(taskList);
19             Console.WriteLine($"{GetFormat("AtomicIntAdd")}, 总耗时:{stopwatch.ElapsedMilliseconds}毫秒, 校验值:{atomicInt}");
20
21             // CEIntAdd
22             taskList = new Task[loopTimes];
23             stopwatch.Restart();
24
25             for (var i = 0; i < loopTimes; i++)
26             {
27                 taskList[i] = Task.Factory.StartNew(() => { CEIntAdd(); });
28             }
29             Task.WaitAll(taskList);
30             Console.WriteLine($"{GetFormat("CEIntAdd")}, 总耗时:{stopwatch.ElapsedMilliseconds}毫秒, 校验值:{ceInt}");
31
32             // LockIntAdd
33             taskList = new Task[loopTimes];
34             stopwatch.Restart();
35
36             for (var i = 0; i < loopTimes; i++)
37             {
38                 taskList[i] = Task.Factory.StartNew(() => { LockIntAdd(); });
39             }
40             Task.WaitAll(taskList);
41             Console.WriteLine($"{GetFormat("LockIntAdd")}, 总耗时:{stopwatch.ElapsedMilliseconds}毫秒, 校验值:{lockInt}");
42
43             // MutexIntAdd
44             taskList = new Task[loopTimes];
45             stopwatch.Restart();
46
47             for (var i = 0; i < loopTimes; i++)
48             {
49                 taskList[i] = Task.Factory.StartNew(() => { MutexIntAdd(); });
50             }
51             Task.WaitAll(taskList);
52             Console.WriteLine($"{GetFormat("MutexIntAdd")}, 总耗时:{stopwatch.ElapsedMilliseconds}毫秒, 校验值:{mutexInt}");
53
54             // LockSlimIntAdd
55             taskList = new Task[loopTimes];
56             stopwatch.Restart();
57
58             for (var i = 0; i < loopTimes; i++)
59             {
60                 taskList[i] = Task.Factory.StartNew(() => { LockSlimIntAdd(); });
61             }
62             Task.WaitAll(taskList);
63             Console.WriteLine($"{GetFormat("LockSlimIntAdd")}, 总耗时:{stopwatch.ElapsedMilliseconds}毫秒, 校验值:{lockSlimInt}");
64
65             // SemaphoreIntAdd
66             taskList = new Task[loopTimes];
67             stopwatch.Restart();
68
69             for (var i = 0; i < loopTimes; i++)
70             {
71                 taskList[i] = Task.Factory.StartNew(() => { SemaphoreIntAdd(); });
72             }
73             Task.WaitAll(taskList);
74             Console.WriteLine($"{GetFormat("SemaphoreIntAdd")}, 总耗时:{stopwatch.ElapsedMilliseconds}毫秒, 校验值:{semaphoreInt}");
75
76
77             // AutoResetEventIntAdd
78             taskList = new Task[loopTimes];
79             stopwatch.Restart();
80
81             for (var i = 0; i < loopTimes; i++)
82             {
83                 taskList[i] = Task.Factory.StartNew(() => { AutoResetEventIntAdd(); });
84             }
85             Task.WaitAll(taskList);
86             Console.WriteLine($"{GetFormat("AutoResetEventIntAdd")}, 总耗时:{stopwatch.ElapsedMilliseconds}毫秒, 校验值:{autoResetEventInt}");
87
88             // NoLockIntAdd
89             taskList = new Task[loopTimes];
90             stopwatch.Restart();
91
92             for (var i = 0; i < loopTimes; i++)
93             {
94                 taskList[i] = Task.Factory.StartNew(() => { NoLockIntAdd(); });
95             }
96             Task.WaitAll(taskList);
97             Console.WriteLine($"{GetFormat("NoLockIntAdd")}, 总耗时:{stopwatch.ElapsedMilliseconds}毫秒, 校验值:{noLockInt}");
98             Console.WriteLine();
99         }

2、线程:10

3、线程:50

三、总结

1)在各种测试中,不加锁肯定是最快的,所以尽量避免资源竞争导致加锁运行

2)在多线程中Interlocked.CompareExchange始终表现出优越的性能,排在第二位

3)第三位lock,临界区也表现出很好的性能,所以在别人说lock性能低的时候请反驳他

4)第四位是原子性变量(Atomic)操作,不过目前只支持变量的自增自减,适用性不强

5)第五位读写锁(ReaderWriterLockSlim)表现也还可以,并且支持无所读,实用性还是比较好的

6)剩下的信号量、事件、互斥量,这三种性能最差,当然他们有各自的适用范围,只是在处理资源竞争这方面表现不好

over,就这样吧,睡觉。。。

原文地址:https://www.cnblogs.com/lanxiaoke/p/11349148.html

时间: 08-13

多线程下解决资源竞争的7种方法的相关文章

Python windows下获取MAC地址的一种方法

我最近有一个项目,使用Python在win32下开发一个COM组建,该COM组建其中一个方法是获取本地电脑的MAC地址. 需求很简单,虽然我是Python新手中的新手,但我还是会使用搜索引擎进行搜索. 百度一下,发现大部分都介绍使用import UUID获取MAC地址,或使用os.popen("ipconfig /all")的方式获取.而后者容易受到操作系统中英文环境影响. 如这篇文章:http://www.cnblogs.com/Jerryshome/archive/2011/11/

在ArcEngine下实现图层属性过滤的两种方法

转自chanyinhelv原文 在ArcEngine下实现图层属性过滤的两种方法 有时候,我们要对图层上的地物进行有选择性的显示,以此来满足实际的功能要求. 按下面介绍的方法可轻松实现图层属性过滤显示: 1.当图层已经加载时 private void ShowByFilter(AxMapControl sMapCtr, IFeatureLayer sFlyr, string sFilter) { ESRI.ArcGIS.Carto.IFeatureLayerDefinition pDef = (

20150503 imx257下实现I2C驱动的四种方法

20150503 imx257下实现I2C驱动的四种方法 2015-05-3 Lover雪儿 时间过得好快,转眼间五一假期就即将结束了,假期期间,大家都潇洒的去玩了,徒留辛辛苦苦的程序员还是窝在宿舍乖乖的敲着代码... 好啦,开开玩笑,辛酸史每家都有一大本,还是要积极的面对生活!!! 今天我们的任务是简单的入门linux内核下i2c设备驱动分离的四种写法. 一.一个简单的i2c驱动 和以前的驱动程序不同,i2c驱动分为drv驱动和dev设备驱动两个文件,不懂的可以参考我以前写的<20150313

ubuntu下部署Django Web的两种方法

一.利用django自带服务器框架发布web 系统管理员经常需要SSH 或者telent 远程登录到Linux 服务器,经常运行一些需要很长时间才能完成的任务,比如系统备份.ftp 传输等等.通常情况下我们都是为每一个这样的任务开一个远程终端窗口,因为它们执行的时间太长了.必须等待它们执行完毕,在此期间不能关掉窗口或者断开连接,否则这个任务就会被杀掉,一切半途而废了.这时screen就可以派上用场了. screen进入一个新建的screen,此时关闭shell窗口,screen为detached

【转】Android下编译jni库的二种方法(含示例) -- 不错

原文网址:http://blog.sina.com.cn/s/blog_3e3fcadd01011384.html 总结如下:两种方法是:1)使用Android源码中的Make系统2)使用NDK(从NDK r5开始) ---------------------------------源码要求: 如果Android工程的顶层目录为my_android_project_name:1) my_android_project_name/jni/Android.mk文件的示例内容如下(以生成libjni_

多线程下解决最大约数个数问题的源代码

#include<iostream> #include<windows.h> #include<time.h> using namespace std; //声明素数表的数组为全局变量 int a[10000]={2,0};//素数表初始化 int flagnumber=0;//记录素数表中的个数 int minnumber=0; int maxnumber=0; //一般来说需要声明将要调用的函数 int Simple_count(int a,int b);//累除法

QThread多线程编程经典案例分析(三种方法,解释了为什么使用moveToThread的根本原因,即为了避免调用QThread::exec() )

传统的图形界面应用程序都只有一个线程执行,并且一次执行一个操作.如果用户调用一个比较耗时的操作,就会冻结界面响应. 一个解决方法是按照事件处理的思路: 调用 Void QApplication::processEvents() 或 void QApplication::processEvents ( int maxtime ) 来强迫事件循环进行,但是这种做法是有潜在风险的.按照QCoreApplication:processEvents()可能会引起递归,导致栈溢出崩溃的说法,当主线程在某个槽

解决EXC_BAD_ACCESS错误的一种方法--NSZombieEnabled

我们做iOS 程序开发时经常用遇到 EXC_BAD_ACCESS 错误导致 Crash,出现这种错误时一般 Xcode 不会给我们太多的信息来定位错误来源,只是在应用 Delegate 上留下像Thread 1: Program received signal:"EXC_BAD_ACCESS",让问题无从找起. 比如你对已释放的对象发送消息时就会出现,EXC_BAD_ACCESS,再如release 的对象再 release,release 那些autorelease 的对象等也会报这

jsp下页面跳转的几种方法小结

1. RequestDispatcher.forward() 在服务器端起作用,当使用forward()时,Servlet engine传递HTTP请求从当前的Servlet或者是JSP到另外的一个Servlet.JSP 或普通HTML文件,也即你的form提交至a.jsp,在a.jsp用到了forward()重定向至b.jsp,此时form提交的所有信息在 b.jsp都可以获得,参数自动传递. 但forward()无法重定向至有frame的jsp文件,可以重定向至有frame的html文件,同

浅谈Linux下简单排除字符串的几种方法(awk、sed、grep)

学习有一段时间了,感觉状态良好,老师所讲的课下也都能想起来,这算是学会(记下)了把. 本着对自己仁慈的起点,出了道十分简单的题考考自己,不上机,手写...结果悲剧满满 题1:有3行文本,要求排除中间行,显示第1行和第3行 模拟环境 [[email protected] tmp]# cat>>a.txt<<EOF            > aa > bb > cc > EOF [[email protected] tmp]# cat a.txt  aa bb