基础数据类型多线程是否需要加锁

Posted God-father

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基础数据类型多线程是否需要加锁相关的知识,希望对你有一定的参考价值。

对于多线程访问同一变量是否需要加锁的问题,先前大家都讨论过。今天用代码验证了一下之前的猜想:32位CPU与内存的最小交换数据为4字节/次,这也是结构体要对齐4字节的原因。在物理上,CPU对于同一4字节的内存单元,不可能写2个字节的同时,又读了3字节。

测试环境为:

XEON 2CPU*2
Windows7

采用50,50,50线程交叉读写,试验代码如下:

C/C++ code
 
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<br data-filtered="filtered">
int g_test;<br data-filtered="filtered">
int temp;<br data-filtered="filtered">
BOOL g_bRunning;<br data-filtered="filtered">
DWORD WINAPI thWriteProc1(LPVOID lParam)<br data-filtered="filtered">
{<br data-filtered="filtered">
    while(g_bRunning)<br data-filtered="filtered">
    {<br data-filtered="filtered">
        g_test = 12345678;<br data-filtered="filtered">
        Sleep(1);<br data-filtered="filtered">
    }<br data-filtered="filtered">
    return 0;<br data-filtered="filtered">
}<br data-filtered="filtered">
DWORD WINAPI thWriteProc2(LPVOID lParam)<br data-filtered="filtered">
{<br data-filtered="filtered">
    while(g_bRunning)<br data-filtered="filtered">
    {<br data-filtered="filtered">
        g_test = 13579246;<br data-filtered="filtered">
        Sleep(1);<br data-filtered="filtered">
    }<br data-filtered="filtered">
    return 0;<br data-filtered="filtered">
}<br data-filtered="filtered">
<br data-filtered="filtered">
DWORD WINAPI thReadProc(LPVOID lParam)<br data-filtered="filtered">
{<br data-filtered="filtered">
    while(g_bRunning)<br data-filtered="filtered">
    {<br data-filtered="filtered">
        temp = g_test;//读取值<br data-filtered="filtered">
        if ( temp != 12345678 && temp != 13579246 )<br data-filtered="filtered">
        {<br data-filtered="filtered">
            g_bRunning = FALSE;<br data-filtered="filtered">
            CString str;<br data-filtered="filtered">
            str.Format("read error!%d", temp);<br data-filtered="filtered">
            AfxMessageBox(str);<br data-filtered="filtered">
            break;<br data-filtered="filtered">
        }<br data-filtered="filtered">
        Sleep(1);<br data-filtered="filtered">
    }<br data-filtered="filtered">
    return 0;<br data-filtered="filtered">
}<br data-filtered="filtered">
void CTestMultiyAccessIntDlg::OnButton1() <br data-filtered="filtered">
{<br data-filtered="filtered">
    g_bRunning = TRUE;<br data-filtered="filtered">
    for int i = 0; i < 50; i++ )<br data-filtered="filtered">
    {<br data-filtered="filtered">
        //创建50个写线程1<br data-filtered="filtered">
        CreateThread( NULL, 0, thWriteProc1, NULL, 0, NULL );<br data-filtered="filtered">
    }<br data-filtered="filtered">
    for int i = 0; i < 50; i++ )<br data-filtered="filtered">
    {<br data-filtered="filtered">
        //创建50个写线程2<br data-filtered="filtered">
        CreateThread( NULL, 0, thWriteProc2, NULL, 0, NULL );<br data-filtered="filtered">
    }<br data-filtered="filtered">
    for int i = 0; i < 50; i++ )<br data-filtered="filtered">
    {<br data-filtered="filtered">
        //创建50个读线程<br data-filtered="filtered">
        CreateThread( NULL, 0, thReadProc, NULL, 0, NULL );<br data-filtered="filtered">
    }<br data-filtered="filtered">
}<br data-filtered="filtered">



测试方法:
改变g_test的类型,给g_test赋予不同的值(不要超过类型的上限值)

测试现象:
当g_test为int,short char时,不存在多线程交叉读写错误的问题
当g_test为double, float, __int64时,存在多线程交叉读写错误的问题,对于__int64,当赋值小于0xFFFFFFFF时不出错,当大于0xFFFFFFFF时出错
当g_test为CString时,存在交叉读写错误,有时候程序崩溃
另:不加Sleep(1)机器卡死过,CPU占用率达到100%,4个核心占用率全满,可以保证运行在多核环境下

现象分析:
(1)int short char均为小于4字节的连续内存块,CPU一条指令就可以读写它们的值,CPU不可能同一个时间执行两条指令
(2)double为8字节,如果写线程先写了4字节,读线程读了8字节,这自然导致数据被破坏
(3)float也为4字节,我也不是太清楚为什么不行,可能是VC对浮点数的处理比较特殊有关,浮点数具有复杂内存结构
(4)__int64为8字节,存在和(2)相同的情况,如果__int64小于等于0xFFFFFFFF,相当于只改变了低4字节,因此就没有问题
(5)CString为类类型,具有复杂结构,显然不行

结论:
1.对于int,short,char,BOOL等小于等于4字节的简单数据类型,如果无逻辑上的先后关系,多线程读写可以完全不用加锁
2.尽管float为4字节,多线程访问时也需要加锁
3.对于大于4字节的简单类型,比如double,__int64等,多线程读写必须加锁。
4.对于所有复杂类型,比如类,结构体,容器等类型必须加锁

尽管对int等类型的多线程读写不需要加锁,但是逻辑上有必要加锁的还是应该加锁
例如:对于一个多线程访问的全局变量int g_test
int count = g_test/1024;
int mod = g_test%1024;
由于是两条语句,执行完第一条之后,别的线程很可能已经修改了g_test的值,如果希望这两条语句执行时,g_test不发生变化,就必须加锁,以保证两条语句执行的整体性。
Lock();
int count = g_test/1024;
int mod= g_test%1024;
UnLock();
如果不加锁,也可以改为先保存到一个临时变量里
int temp = g_test;
int count = temp/1024;
int mod = temp%1024;

以上是关于基础数据类型多线程是否需要加锁的主要内容,如果未能解决你的问题,请参考以下文章

JAVA多线程基础

C++11多线程 原子操作概念及范例

多线程基础

计算机基础知识整理

python多线程全局变量和锁

java基础--25.多线程的改进--Lock显式地加锁和释放锁