博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
线程安全
阅读量:5846 次
发布时间:2019-06-18

本文共 2223 字,大约阅读时间需要 7 分钟。

最近在看《程序员的自我修养》,做一下笔记。

原子操作

典型的例子就是++i这种,看着像是一条语句,其实编译器会把它翻译成多条执行命令,让操作系统执行。i++的汇编语句执行过程:

1) 读取i到某个寄存器X

2) X++

3) 将X的内容存储回i。

所以两个线程同时操作i的时候,会出现交叉赋值的情况,使执行结果变得未知。

我们把汇编语句层面的单条指令的操作成为原子。

在Windows中,有一套API专门进行一些原子操作,这些API成为Interlocked API。

 

编写可重入函数,保证线程安全

一个函数被重入,表示这个函数没有执行完成,由于外部因素或内部调用,又一次进入该函数执行。一个函数要被重入,只有两种情况:

1)多个线程同时执行这个函数

2) 函数自身调用自身

 

一个函数被称为可重入,表明该函数被重用之后不会产生任何不良后果。举例:

int sqr(int i){   return i*i;}

 可重入函数的特点:

1) 不使用任何(局部)静态或全局的非const变量。

2) 不返回任何(局部)静态或全局的非const变量的指针。

3) 仅依赖于调用方提供的参数,

4) 不依赖任何单个的资源的锁

5) 不调用任何不可重入的函数

可重入是并发安全的强力保障,一个可重入的函数可以在多线程环境下放心使用

过度优化

过度优化不是指我们自己优化过度。而是指编译器或者操作系统帮我们进行偷偷的优化,导致我们的程序在多线程下出现一些怪异的情况。

x = 0Thread1                  Thread2lock()                   lock()x++;                     x++;unlock()                 unlock()

 上面提到过的,现在用锁给保护了,X++的行为不会被并发所破坏。那么X的值必然可以预测。

然而,如果编译器为了提高X的访问速度,把X放到某个寄存器里,由于不同线程的寄存器是各自独立的。因此如果Thread1先获得锁,则程序的执行可能会出现如下的情况:

可见这样的情况下即使正确的加锁,也不能保证多线程安全。

例子2

x=y=0Thread1            Thread2x=1;               y=1;r1=y;              r2=x;

 很显然,r1和r2至少有一个为1,逻辑上不可能同时为0.

然而,事实上r1=r2=0的情况确实可能发生。原因在于十几年前,CPU就发展出了动态调度,在执行程序的时候为了提高效率可能交换指令的顺序。同样,编译器在进行优化的时候,也可能为了效率而交换毫不相干的两条相邻执行(如x=1和r1=y)的执行顺序。以上代码执行的时候可能是这样的:

x=y=0;Thread1            Thread2r1=y;              y=1;x=1;               r2=x;

 那么r1=r2=0就完全可能了。我们可以使用volatile关键字试图阻止过度优化,volatile基本可以做到两件事情:

1)阻止编译器为了提高速度将一个变量缓存到寄存器内而不回写。

2) 阻止编译器调整操作volatile变量的指令顺序。

可见volatile可以解决编译器层面的顺序调整问题,但是无法阻止CPU动态调度换序

例3

单例模式的实现 https://www.cnblogs.com/myd620/p/6133420.html

volatile T *pInst = 0;T *getInstance(){    if(pInst == NULL)   {        lock();        if(pInst == NULL)        {            pInst = new T;        }       unlock();   }  return pInst;}

上面代码双重if在这里另有妙用,可以让lock的调用开销降低到最小。

问题剖析

问题来源仍然是CPU的乱序执行。

C++里的new其实包含两个步骤:

1)  分配内存

2)调用构造函数

所以pInst = new T包含了三个步骤:

1) 分配内存

2)在内存的位置上调用构造函数

3) 将内存的地址赋值给pInst

在这三步中,2)和3)是可以颠倒的。也就是说,完全可以出现pInst的值已经不是NULL, 但对象仍然没有构造完毕,这时候如果出现另外一个对getInstance的并发调用,此时第一个if内的表达式pInst==NULL为false,会返回一个为构造对象的地址给用户,当然了,程序就崩溃了。

如何解决这个问题呢?

volatile T *pInst = 0;T *getInstance(){    if(pInst == NULL)   {        lock();        if(pInst == NULL)        {            T* temp = new T;            pInst  = temp;        }       unlock();   }  return pInst;}

 

转载于:https://www.cnblogs.com/xzlq/p/8795103.html

你可能感兴趣的文章
我的友情链接
查看>>
搭建openstack,报:Error: unable to connect to node rabbit@localhost: nodedown
查看>>
团队正能量读书笔记
查看>>
自动接听挂断电话
查看>>
生成器
查看>>
基础篇19章综合练习题
查看>>
python中的问号表达式
查看>>
java 测试IP
查看>>
C#实现ActiveX控件开发与部署
查看>>
用CSS做导航菜单的4个理由
查看>>
OAuth授权登录
查看>>
转:Chrome不支持showModalDialog模态对话框和无法返回returnValue的问题
查看>>
mysql优化综合(转)
查看>>
BZOJ5072:[Lydsy1710月赛]小A的树(树形DP)
查看>>
单链表及其算法演示(Data structure course from HaoBin)
查看>>
Day6-Dhcp
查看>>
NOIP2015 运输计划 二分答案+Tarjan LCA+树上差分
查看>>
构建之法读后感
查看>>
hdu题型分类
查看>>
08.LoT.UI 前后台通用框架分解系列之——多样的Tag选择器
查看>>