HTML5技术

程序员的自我修养一温故而知新 - 目不识丁(3)

字号+ 作者:H5之家 来源:H5之家 2017-08-03 13:02 我要评论( )

互斥量:和二元信号量很类似,但和信号量不同的是:信号量在一个系统中,可以被任意线程获取或释放。互斥量要求那个线程获取互斥量,那么哪个线程就释放互斥量,其他线程释放无效。 临界区:比互斥量更加严格的手段

互斥量:和二元信号量很类似,但和信号量不同的是:信号量在一个系统中,可以被任意线程获取或释放。互斥量要求那个线程获取互斥量,那么哪个线程就释放互斥量,其他线程释放无效。
临界区:比互斥量更加严格的手段。把临界区的锁获取称为进入临界区,而把锁的释放称为离开临界区。临界区和互斥量,信号量区别在与互斥量,信号量在系统中任意进程都是可见的。临界区的作用范围仅限于本线程,其他线程无法获取。其他性质与互斥量相同。
读写锁:致力于一种更加特定的场合的同步。如果使用之前使用的信号量、互斥量或临界区中的任何一种进行同步,对于读取频繁,而仅仅是偶尔写入的情况会显得非常低效。读写锁可以避免这个问题。对于同一个锁,读写锁有两种获取方式:

  • 共享的
  • 独占的
  • 读写锁的总结

    读写锁状态以共享方式获取以独占方式获取

    自由 成功 成功

    共享 成功 等待

    独占 等待 等待

    条件变量:作为同步的手段,作用类似于一个栅栏。对于条件变量,线程有两个操作:

  • 线程可以等待条件变量,一个条件变量可以被多个线程等待
  • 线程可以唤醒条件变量,此时某个或所有等待此条件变量的线程都会被唤醒并继续支持
  • 使用条件变量可以让许多线程一起等待某个事件的发生,当事件发生时,所有线程可以一起恢复执行。

    可重入与线程安全

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

  • 多个线程同时执行这个函数
  • 函数自身(可能经过多层调用之后)调用自身
  • 一个函数被称为可重入,表示重入之后不会产生任何不良影响

    可重入函数:

    1 int sqr(int x) 2 { 3 return x*x; 4 }

    一个函数要成为可重入,必须具有如下特点:

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

    过度优化

    有时候合理的合理的使用了锁也不一定能保证线程的安全。

    //Thread1 x=0; lock(); x++; unlock(); //Thread2 x=0; lock(); x++; unlock();

    上面X的值应该为2,但如果编译器为了提高X的访问速度,把X放到了某个寄存器里面,不同线程的寄存器是各自独立的,因此,如果Thread1先获得锁,则程序的执行可能会呈现如下:
    [Thread1]读取x的值到某个寄存器R [1] (R[1]=0);
    [Thread1]R[1]++(由于之后可能要访问到x,所以Thread1暂时不将R[1]写回x);
    [Thread2]读取x的值到某个寄存器R[2] (R[2]=0);
    [Thread2]R[2]++(R[2]=1);
    [Thread2]将R[2]写回至x(x=1);
    [Thread1] (很久以后)将R[1]写回至x(x=1);
    如果这样,即使加锁也不能保证线程安全

    x=y=0; //Thread1 x=1; r1=y; //Thread2 y=1; r2=x;

    上面代码有可能发生r1=r2=0的情况。
    CPU动态调度:在执行程序的时候,为了提高效率有可能交换指令的顺序。
    编译器在进行优化的时候,也可能为了效率交换两个毫不相干的相邻指令的执行顺序。
    上面代码执行顺序可能是这样:

    x=y=0; [Thread1] r1=y; x=1; [Thread2] y=1; r2=x;

    使用volatile关键字可以阻止过度优化,colatile可以做两件事情:

  • 阻止编译器为了提高速度将一个变量缓存到寄存器内而不写回
  • 阻止编译器调整操作volatile变量的指令顺序
  • 但volatile无法阻止CPU动态调度换序
    C++中,单例模式。

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

    CPU的乱序执行可能会对上面代码照成影响
    C++里的new包含两个步骤:

  • 分配内存
  • 调用构造函数
  • 所以pInst=new T包含三个步骤:

    这三步中2和3的步骤可以颠倒,可能出现这种情况:pInst中的值不是NULL,但对象还是没有构造完成。
    要阻止CPU换序,可以调用一条指令,这条指令常常被称为barrier:它会阻止CPU将该指令之前的指令交换到barrier之后。
    许多体系的CPU都提供了barrier指令,不过,它们的名称各不相同。例如POWERPC提供的指令就叫做lwsync。所以我们可以这样保证线程安全:

    #define barrier() __asm__ volatile ("lwsync") volatile T* pInst=0; T* GetInstance() { if(pInst==NULL) { LOCK(); if(pInst==NULL) { T* temp=new T; barrier(); pInst=temp; } unlock(); } return pInst; }

    1.6.3多线程的内部情况

     

    1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

    相关文章
    • 程序员招聘感悟 - 灵隐心

      程序员招聘感悟 - 灵隐心

      2017-08-01 09:01

    • net.sz.framework 框架 ORM 消消乐超过亿条数据排行榜分析 天王盖地虎 - 失足程序员

      net.sz.framework 框架 ORM 消消乐超过亿条数据排行榜分析 天王盖地

      2017-07-26 15:00

    • 浅谈程序员该具备的自我修养 - Amedeo

      浅谈程序员该具备的自我修养 - Amedeo

      2017-07-23 08:00

    • 程序员装修指南 - 无知者云

      程序员装修指南 - 无知者云

      2017-07-19 16:00

    网友点评
    c