通常在涉及到多线程和多进程操作共享数据时候,不可避免的会涉及到公共数据的互斥访问,可能会用到互斥锁,事件,信号量等,然而在使用这些锁机制的同时肯定会降低系统的并发性,同时如果使用不当可能还会造成一些死锁等很让人反感的问题。
其实大部分时候可以通过一些细节的设计来避免锁机制,共享数据的互斥访问总的来说可以分为以下两种:
1、同时修改
先来个例子说明下同时修改数据造成数据不一致的问题:
假设有A,B两个进程对共享内存中的变量m = 10进行自加运算,当A进程将变量m = 10从内存读入cpu的寄存器后,这时B进程也将m = 10读入寄存器,
那么A进程加运算得到11,B进程也读入10得到11,最终A和B进程将11从寄存器写回内存,那么m=11,本来做了两次自增,却只加了1,肯定不是我们要的结果。
通常直接的解决办法是使用互斥锁,自然可以避免这种情况。
如果要避免使用锁机制, 可以考虑这么做:
在设计的时候就需要对系统各个模块的功能做比较明确的划分,每一个模块只负责修改自己的数据,通过发送消息通知其他模块修改数据。
比如从业务逻辑上A进程只负责共享数据段1的修改,B进程只负责共享数据段2的修改,那么当B进程需要修改数据段1的数据时候,发送消息给A进程,让A进程去修改,同样
A进程对数据段2的修改也通过B去修改。
总之就是要从业务逻辑上将各个进程的功能分开,那每个进程修改自己的共享段数据,那么就不需要担心同时修改造成的数据不一致问题了。在这里设为共享是为了方便访问对方的数据,下面会讲写入和读取时候怎么保证。
2、一边写入一边读取
如果操作不当,这个情况下,通常会出现读取的那一方读取的数据不完整。
我们知道CPU每次从内存读取或者写入的数据跟CPU的引脚数有关,如果是32位的计算机,也就是CPU引脚有32个,一次能读取或者写入32位数据,那么下面的程序是不会存在数据不完整的问题:
公共数据:
int m = 0;
进程A:
while(true)
{
m++;
}
进程B:
if(m%10 == 0)
{
...
}
因为,一个int类型的数据占4字节,也就是32位,那么在32引脚的cpu上,从寄存器通过总线设备写回内存时候肯定是一次写回来,也就是B进程访问变量m时候,m不会出现不完整的情况。
这个写入的过程有点类似原子操作(当然,A进程从读取m到写回内存,肯定不是原子操作),只是在这个过程中,数据是处于一个完整状态。
再说说一个数据不完整,会被撕裂的例子,在32位的计算机上:
公共数据:
INT64 m = 0x00000000bbbbbbbb;
进程A:
while(true)
{
m = m & 0xaaaaaaaaffffffff;
}
进程B:
if(m%10 == 0)
{
...
}
与上个程序唯一不同的是m的类型64位的,也就是说在32位引脚cpu上,A进程将改变m的值后,从寄存器写回内存,至少需要写入两次,假设第一次写入高32位,oxaaaaaaaa,第二次写入减1后的低32位,0xbbbbbbbc,那么如果A进程刚写完高位数据回内存,时间片切换到B进程,B在读取的时候可能就会读取到一半的数据的情况,可能还是之前的值0xaaaaaaaabbbbbbbb,这样数据就会被撕裂,访问的数据不完整。
如果是在64位的计算机上(当然操作系统也要64位啊,不然32位的系统可能还是分两次写入),就不会存在这种情况。
那么如何避免这种情况呢:
使用dirty机制:具体的做法就是使用一个变量来判断我们需要访问的数据的状态,还是上面的例子,代码如下:
公共数据:
class Data
{
public:
int a;
int b;
};
Data data;
int dirty = 0;
进程A:
while(true)
{
if(dirty == 0)
{
data.a = 100;
data.b = 200;
dirty = 1;
}
}
进程B:
/* 检测数据是否被改变过 */
while(true)
{
if(dirty == 1)
{
int a = data.a;
int b = data.b;
dirty = 0;
....
}
}
上面已经说过,对dirty的访问,不存在不完整的情况,因此大部分时候可以通过对dirty机制,来解决数据一边读一边写带来的不完整性。
当然上面这个程序在效率方面和使用锁机制基本差不多,因为A进程和B进程都需要依赖对方去做操作,那么当条件不满足的时候,在自己的时间片内都不会改或者读数据。
其实,如果A进程不频繁修改数据,那么B进程通过dirty机制判断,效率肯定是比使用锁机制高的。
因此,在多线程或者多进程的程序中,设计的时候如果能够避免锁机制,可以尽量避免使用锁机制,可以降低系统的复杂性,避免锁机制带来的死锁等问题,而且大部分时候还可以提高系统的并发性。
第一次写blog,希望以后能把自己做开发过程中的一些东西多总结,如果有什么不对的地方,欢迎指出。