zoukankan      html  css  js  c++  java
  • 多线程和多进程中避免锁机制注意的几点

      通常在涉及到多线程和多进程操作共享数据时候,不可避免的会涉及到公共数据的互斥访问,可能会用到互斥锁,事件,信号量等,然而在使用这些锁机制的同时肯定会降低系统的并发性,同时如果使用不当可能还会造成一些死锁等很让人反感的问题。

    其实大部分时候可以通过一些细节的设计来避免锁机制,共享数据的互斥访问总的来说可以分为以下两种:

    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,希望以后能把自己做开发过程中的一些东西多总结,如果有什么不对的地方,欢迎指出。

  • 相关阅读:
    如何做好不擅长的测试任务
    [ Python入门教程 ] Python中日志记录模块logging使用实例
    [ Python入门教程 ] Python中日期时间datetime模块使用实例
    cmd命令行窗口和文件目录资源管理器快速切换
    [ Python入门教程 ] Python中JSON模块基本使用方法
    [ PyQt入门教程 ] PyQt5中多线程模块QThread使用方法
    [ PyQt入门教程 ] PyQt5中数据表格控件QTableWidget使用方法
    设计模式目录
    我的软考架构师之路:目录(共22篇)
    算法篇:目录
  • 原文地址:https://www.cnblogs.com/jiangwang2013/p/3398272.html
Copyright © 2011-2022 走看看