频道栏目
首页 > 资讯 > 其他 > 正文

多线程的那点事儿(之数据互斥)

11-11-26        来源:[db:作者]  
收藏   我要投稿

 

【 声明:版权所有,欢迎转载,请勿用于商业用途。  联系信箱:feixiaoxing @163.com】

 

 

 

    在多线程存在的环境中,除了堆栈中的临时数据之外,所有的数据都是共享的。如果我们需要线程之间正确地运行,那么务必需要保证公共数据的执行和计算是正确的。简单一点说,就是保证数据在执行的时候必须是互斥的。否则,如果两个或者多个线程在同一时刻对数据进行了操作,那么后果是不可想象的。

 

    也许有的朋友会说,不光数据需要保护,代码也需要保护。提出这个观点的朋友只看到了数据访问互斥的表象。在程序的运行空间里面,什么最重要的呢?代码吗?当然不是。代码只是为了数据的访问存在的。数据才是我们一切工作的出发点和落脚点。

 

    那么,有什么办法可以保证在某一时刻只有一个线程对数据进行操作呢?四个基本方法:

 

    (1)关中断

 

    (2)数学互斥方法

 

    (3)操作系统提供的互斥方法

 

    (4)cpu原子操作

 

    为了让大家可以对这四种方法有详细的认识,我们可以进行详细的介绍。

 

  

 

    (1)关中断

 

    要让数据在某一时刻只被一个线程访问,方法之一就是停止线程调度就可以了。那么怎样停止线程调度呢?那么关掉时钟中断就可以了啊。在X86里面的确存在这样的两个指令,

 

#include <stdio.h>  

 

int main() 

    __asm{ 

        cli 

        sti 

    } 

    return 1; 

#include <stdio.h>

 

int main()

{

       __asm{

              cli

              sti

       }

       return 1;

}

    其中cli是关中断,sti是开中断。这段代码没有什么问题,可以编过,当然也可以生成执行文件。但是在执行的时候会出现一个异常告警:Unhandled exception in test.exe: 0xC0000096:  Privileged Instruction。告警已经说的很清楚了,这是一个特权指令。只有系统或者内核本身才可以使用这个指令。

 

    不过,大家也可以想象一下。因为平常我们编写的程序都是应用级别的程序,要是每个程序都是用这些代码,那不乱了套了。比如说,你不小心安装一个低质量的软件,说不定什么时候把你的中断关了,这样你的网络就断了,你的输入就没有回应了,你的音乐什么都没有了,这样的环境你受的了吗?应用层的软件是千差万别的,软件的水平也是参差不齐的,所以系统不可能相信任何一个私有软件,它相信的只是它自己。

 

 

 

    (2)数学方法

 

    假设有两个线程(a、b)正要对一个共享数据进行访问,那么怎么做到他们之间的互斥的呢?其实我们可以这么做,

 

unsigned int flag[2] = {0}; 

unsigned int turn = 0; 

 

void process(unsigned int index) 

    flag[index] = 1; 

    turn =  index; 

 

    while(flag[1 - index] && (turn ==  index)); 

    do_something(); 

    flag[index] = 0; 

unsigned int flag[2] = {0};

unsigned int turn = 0;

 

void process(unsigned int index)

{

       flag[index] = 1;

       turn =  index;

 

       while(flag[1 - index] && (turn ==  index));

       do_something();

       flag[index] = 0;

}

    其实,学过操作系统的朋友都知道,上面的算法其实就是Peterson算法,可惜它只能用于两个线程的数据互斥。当然,这个算法还可以推广到更多线程之间的互斥,那就是bakery算法。但是数学算法有两个缺点:

 

    a)占有空间多,两个线程就要flag占两个单位空间,那么n个线程就要n个flag空间,

 

    b)代码编写复杂,考虑的情况比较复杂

 

 

 

    (3)系统提供的互斥算法

 

    系统提供的互斥算法其实是我们平时开发中用的最多的互斥工具。就拿windows来说,关于互斥的工具就有临界区、互斥量、信号量等等。这类算法有一个特点,那就是都是依据系统提高的互斥资源,那么系统又是怎么完成这些功能的呢?其实也不难。

 

    系统加锁过程,

 

void Lock(HANDLE hLock) 

    __asm {cli}; 

 

    while(1){ 

        if(/* 锁可用*/){ 

            /* 设定标志,表明当前锁已被占用*/ 

            __asm {sti}; 

            return; 

        } 

 

        __asm{sti}; 

        schedule(); 

        __asm{cli}; 

    } 

void Lock(HANDLE hLock)

{

       __asm {cli};

 

       while(1){

              if(/* 锁可用*/){

                     /* 设定标志,表明当前锁已被占用*/

                     __asm {sti};

                     return;

              }

 

              __asm{sti};

              schedule();

              __asm{cli};

       }

}

    系统解锁过程,

 

void UnLock(HANDLE hLock) 

    __asm {cli}; 

    /* 设定标志, 当前锁可用*/ 

    __asm{sti}; 

void UnLock(HANDLE hLock)

{

       __asm {cli};

       /* 设定标志, 当前锁可用*/

       __asm{sti};

}

    上面其实讨论的就是一种最简单的系统锁情况。中间没有涉及到就绪线程的压入和弹出过程,没有涉及到资源个数的问题,所以不是很复杂。朋友们仔细看看,应该都可以明白代码表达的是什么意思。

 

 

 

    (4)CPU的原子操作

    因为在多线程操作当中,有很大一部分是比较、自增、自减等简单操作。因为需要互斥的代码很少,所以使用互斥量、信号量并不合算。因此,CPU厂商为了开发的方便,把一些常用的指令设计成了原子指令,在windows上面也被称为原子锁,常用的原子操作函数有

 

InterLockedAdd 

 

InterLockedExchange 

 

InterLockedCompareExchange 

 

InterLockedIncrement 

 

InterLockedDecrement 

 

InterLockedAnd 

 

InterLockedOr 

InterLockedAdd

 

InterLockedExchange

 

InterLockedCompareExchange

 

InterLockedIncrement

 

InterLockedDecrement

 

InterLockedAnd

 

InterLockedOr

www.2cto.com

相关TAG标签
上一篇:台积电:绝大多数7nm客户都会转向6nm_IT新闻_博客园
下一篇:最后一页
相关文章
图文推荐

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站