mutex和CRITICAL_SECTION,互斥和临界区

Posted 武装三藏

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mutex和CRITICAL_SECTION,互斥和临界区相关的知识,希望对你有一定的参考价值。

本文不没有任何知识可讲,只是帖上自己测试的结果。

想看底层原理的可以直接关闭。

不过对于急着要选方案的人,倒提供一些帮助。

先说一些无关紧要的废话:

====================================================================================================================================================

先说说为什么会有这篇文章。

我在做练习的时候,参考一些老代码,发现了CRITICAL_SECTION这个类型。以前没有用过。查了一下,三个要点:windows使用;互斥效果;比mutex快。

后来又翻了些网页查看两者的一些简介。很统一的结果,CRITICAL_SECTION比mutex快,而且Linux上没有类似的接口(注:可能是我搜索的方式不对,加上本人对Linux研究不多,所以没有找到)。

对于刚打算使用c++11制作全新技术接口版本的服务器的我来说,很遗憾啊,Linux没有。难道我只能用这种慢速的锁?而我又不想写太多差异化的代码,能用标准库最好,等到真的某个模块成为新能瓶颈的时候再针对某个模块在Linux上做差异处理。

所以我还是想使用标准库来完成这个事情。

于是,我搜索“CRITICAL_SECTION c++11”。

两篇很重要的文章出现在了搜索结果里,是因为这两篇文章而产生了本文。

https://stackoverflow.com/questions/23519630/are-there-c11-critical-sections

https://stackoverflow.com/questions/9997473/stdmutex-performance-compared-to-win32-critical-section

====================================================================================================================================================

第一篇文章的精要在:

虽然Linux上没有临界区这样的接口,而mutex又是需要陷入内核去处理的东西。但是呢这些都是规定,仅仅是为了兼容POSIX协议做的。而mutex慢主要是POSIX需要跨进程。但是呢,在不同的系统和版本上面,就可以有私人定制,就如同windows上的CRITICAL_SECTION。一旦我不再兼容POSIX,就可以做一些自己的花活。而同时在兼容POSIX的平台上,继续遵循POSIX的规定。

以上精要,你可以在第一个连接的第一个回复里面的追问里面得到。

这给我提供了一个很重要的信息:c++11是没有临界区这样的用法。而且mutex的跨进程也不是所有的系统和版本都需要的,仅仅是某些版本需要。在不需要的版本上std::mutex可能是有特殊的用法和优化可以媲美临界区。

总之,mutex和mutex不一样

有了这个想法,我决定自己写代码试试。

然而不幸的是,当我准备写的时候,我想,这种问题应该也会有其他人这样想吧,说不定能搜到呢?

在搜索结果里,我就看到了第二篇。

第二篇文章的精要在:

std::mutex慢。CRITICAL_SECTION更快。但是如果采用合理的方式来分割任务,两者可以达到几乎相同的效果。

第二篇文章是含有两个人的测试代码的。第一个人的测试代码是直接比对两种用法的时间差异。但是很遗憾,他使用的是vs2012。这个版本对c++11的支持并不算完美。第二个人的测试代码是将任务做了分割,分给不同的cpu,又延长了执行间隔,减少访问冲突。使用的是vs2013,这一传说中对c++11支持很完善的版本

看到这里,我有些冷,就不太想写测试代码了。原因是开发工具,人家已经更新到了一个合理的版本,其次在结构上进行了划分,而划分之后才打个平手。

似乎所有的结果都是唯一的。

但是!中间好几年了。万一有变化呢?即使没有,自己测试一下总归实在一些。所以还是自己做了个测试。结果很意外。

先说思路:

在同一进程中,开启4个线程,2个用std::mutext去抢,两个用CRITICAL_SECTION去抢;

两组方式各自使用自己组的变量;

只记录计算次数,不做结果正确判断;

以下是测试代码

 1 #include <iostream>
 2 #include <mutex>
 3 #include <thread>
 4 #include <Windows.h>
 5 #include <chrono>
 6 
 7 using namespace std;
 8 
 9 mutex g_Mutex_Lock, g_Mutex_finish;
10 CRITICAL_SECTION g_CS_Lock, g_CS_finish;
11 uint64_t g_Mutext_Num = -1;
12 uint64_t g_CS_Num = -1;
13 const int32_t g_Count = 10000000;
14 once_flag g_Mutex_flag, g_CS_flag;
15 chrono::time_point<chrono::system_clock> g_Mutex_StartTime, g_CS_StartTime;
16 int32_t g_Mutex_Complete = 0;
17 int32_t g_CS_Complete = 0;
18 
19 uint64_t Calculate(uint64_t num, int index)
20 {
21     if (index % 2)
22     {
23         return (num / 0x5555) * 0xaaaa;
24     }
25     else
26     {
27         return (num / 0x6666) * 0x9999;
28     }
29 }
30 
31 void mutexTimeStart()
32 {
33     g_Mutex_StartTime = chrono::system_clock::now();
34 }
35 
36 void mutexCalculate()
37 {
38     call_once(g_Mutex_flag, mutexTimeStart);
39 
40     for (int i = 0; i < g_Count; ++i)
41     {
42         g_Mutex_Lock.lock();
43         g_Mutext_Num = Calculate(g_Mutext_Num, i);
44         g_Mutex_Lock.unlock();
45     }
46     g_Mutex_finish.lock();
47     ++g_Mutex_Complete;
48     if (2 == g_Mutex_Complete)
49     {
50         chrono::duration<double> elapsed_seconds = chrono::system_clock::now() - g_Mutex_StartTime;
51         printf("mutex finished use: %f\\n", elapsed_seconds.count());
52     }
53     g_Mutex_finish.unlock();
54 }
55 
56 void csTimeStart()
57 {
58     g_CS_StartTime = chrono::system_clock::now();
59 }
60 
61 void csCalculate()
62 {
63     call_once(g_CS_flag, csTimeStart);
64     for (int i = 0; i < g_Count; ++i)
65     {
66         EnterCriticalSection(&g_CS_Lock);
67         g_CS_Num = Calculate(g_CS_Num, i);
68         LeaveCriticalSection(&g_CS_Lock);
69     }
70     EnterCriticalSection(&g_CS_finish);
71     ++g_CS_Complete;
72     if (2 == g_CS_Complete)
73     {
74         chrono::duration<double> elapsed_seconds = chrono::system_clock::now() - g_CS_StartTime;
75         printf("cs finished use: %f\\n", elapsed_seconds.count());
76     }
77     LeaveCriticalSection(&g_CS_finish);
78 }
79 
80 
81 void main()
82 {
83     InitializeCriticalSection(&g_CS_Lock);
84     InitializeCriticalSection(&g_CS_finish);
85 
86     thread t3(csCalculate);
87     t3.detach();
88     thread t4(csCalculate);
89     t4.detach();
90 
91     thread t1(mutexCalculate);
92     t1.detach();
93     thread t2(mutexCalculate);
94     t2.detach();
95 
96     int tStop;
97     cin >> tStop;
98 }
main.cpp

测试环境:win10企业版(已经更新到最新)+vs2015企业版+i7-6700HQ(2.6G×8)

64位release版结果:

图中除最后一个外,都是循环1千万次的结果。最后一个是10亿次的结果。

再上一个64位debug版的1亿次的截图(原谅我没有等带10亿次的结果,你们不知道,我测试1千万的结果是n秒。然后头绕一热直接跳了两级,一运行,发现没出结果,然后一算,就傻了,关掉减个0)。

无论前面有多少经历,无论多少推测。结果胜过一切,我可以继续安心的、开心的使用std继续进行我的练习了。

本次测试结果:

1、性能不是瓶颈,不要考虑太多。优化都是在原有的基础上逐步修改改出来的成果,不是动手的时候,脑子就有现成的方案。何况性能并没有走到瓶颈。

2、没有什么比电脑跑出来的结果更靠谱。毕竟电脑才是所有理论知识最终产物的执行者。

3、随时间的推移,技术在改良。使用通用的接口,每次技术更替,你也在享受免费的红利。

最后,如果有朋友发现我的代码中存在影响测试结果的错误,请留言指出。我不想自己错了,还误导别人。

以上是关于mutex和CRITICAL_SECTION,互斥和临界区的主要内容,如果未能解决你的问题,请参考以下文章

第29课 互斥量与自解锁(std::mutex和lock系列)

互斥锁的示例

6.5 linux内核互斥锁

21 同步与互斥互斥量

45. sync.Mutex 互斥和互斥锁 | 厚土Go学习笔记

Go基础系列:互斥锁Mutex和读写锁RWMutex用法详述