多线程编程之数据访问互斥

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

  保证多线程之间的数据访问互斥,有以下四类方法:

  (1)关中断

  (2)数学互斥方法

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

  (4)CPU原子操作

  下面针对这四种方法进行详细说明:

(1)关中断

  既然多线程之间的中断切换会导致访问同一数据的不同步,那么关闭线程中断切换肯定能过避免这个问题。而且,Intel X86系列CPU中确实存在这样的关闭中断指令。参照如下代码:

#include <stdio.h>
int main()
{
    __asm{
        cli
        sti
    }
    return 1;
}

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

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

(2)数据方法

  通过某个数学算法,可以确保不同的线程之间只可能其中一个访问某个数据。例如有两个线程操作同一个变量,可以采用如下算法:

unsigned int flag[2] = {0};
unsigned int turn = 0;

void process(unsigned int index)
{
    flag[index] = 1;
    turn =  1 - index;

    while(flag[1 - index] && (turn == (1 - 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 UnLock(HANDLE hLock)
{
    __asm {cli};
    /* 设定标志, 当前锁可用 */
    __asm{sti};
}

  从代码中可以看出,采用的CPU的中断关闭与开启指令就能够实现一个简单的系统锁。不过这个例子没有考虑就绪线程的压栈等问题,实际情况会更加复杂些。

(4)CPU原子操作

  在多线程中经常会涉及到一个经常用到而又非常简单的计算操作,这个时候使用互斥量、信号量等实现互斥操作显得不划算。因此,CPU厂商将一些常用的操作设计成原子指令,在Windows系统中也称之为原子锁。常用的原子操作包括:

InterLockedAdd
InterLockedExchange
InterLockedCompareExchange
InterLockedIncrement
InterLockedDecrement
InterLockedAnd
InterLockedOr
时间: 2024-10-26 01:43:15

多线程编程之数据访问互斥的相关文章

【C/C++多线程编程之六】pthread互斥量

多线程编程之线程同步互斥量  Pthread是 POSIX threads 的简称,是POSIX的线程标准. Pthread线程同步指多个线程协调地,有序地同步使用共享资源.[C/C++多线程编程之五]pthread线程深入理解中讲述到,多线程共享进程资源,一个线程访问共享资源需要一段完整地时间才能完成其读写操作,如果在这段时间内被其他线程打断,就会产生各种不可预知的错误.协调线程按一定的规则,不受打扰地访问共享资源,保证正确性,这便是线程同步的出发点.        互斥量,是最简单的线程同步

【原】多线程编程中临界区与互斥锁的区别

临界区和互斥锁的区别1.临界区只能用于对象在同一进程里线程间的互斥访问:互斥锁可以用于对象进程间或线程间的互斥访问.2.临界区是非内核对象,只在用户态进行锁操作,速度快:互斥锁是内核对象,在核心态进行锁操作,速度慢.3.临界区和互斥体在Windows平台都下可用:Linux下只有互斥锁可用

多线程编程之原子锁

在<多线程编程之数据访问互斥>一文中简单介绍了原子锁,这里再详细说一下原子锁的概念和用途. (1)简单数据操作 如果在一个多线程环境下对某个变量进行简单数学运算或者逻辑运算,那么就应该使用原子锁操作.因为,使用临界区.互斥量等线程互斥方式将涉及到很多操作系统调用和函数调用等,效率肯定不如原子操作高.比如有这样一个例子: unsigned int count = 0; int data_process() { if(/* conditions */){ EnterCriticalSection(

【C/C++多线程编程之十】pthread线程私有数据

多线程编程之线程私有数据 Pthread是 POSIX threads 的简称,是POSIX的线程标准.  线程同步从互斥量[C/C++多线程编程之六]pthread互斥量,信号量[C/C++多线程编程之七]pthread信号量,条件变量[C/C++多线程编程之八]pthread条件变量,读写锁[C/C++多线程编程之九]pthread读写锁,多线程的同步机制已经有了清晰深入的探究,多线程编程的精髓所在,需要深入理解.        线程私有数据TSD(Thread-specific Data)

Linux高性能服务器编程——多线程编程(下)

多线程编程 条件变量 如果说互斥锁是用于同步线程对共享数据的访问的话,那么条件变量则是用于线程之间同步共享数据的值.条件变量提供了一种线程间的通信机制:当某个共享数据达到某个值得时候,唤醒等待这个共享数据的线程. 条件本身是由互斥量保护的.线程在改变条件状态前必须首先锁住互斥量,其他现成在获得互斥量之前不会察觉到这种变化,因为必须锁住互斥量以后才能计算条件. 条件变量的相关函数主要有如下5个: #include <pthread.h> int pthread_cond_destroy(pthr

回顾一下synchronized关键字,多线程编程的思路

写过 JAVA 并发代码的同学对 synchronized 关键字一定是熟的不能再熟了,其基于对象头部的 monitor 实现了对代码块的加锁,使一段代码变为线程不可重入的. synchronized 与操作系统层的 lock 与 unlock 机制非常类似,多线程通过一个共享变量通信,这个共享变量标志着一段代码是否正在被一段线程执行着,如果已有线程在执行这段代码了,那么其它线程便等待在这个信号量上,直到其执行完毕并重置信号量. 操作系统层级实现 lock/unlock 有两种方式,一是单核环境

【C/C++多线程编程之八】pthread条件变量

多线程编程之条件变量 Pthread是 POSIX threads 的简称,是POSIX的线程标准. 互斥机制,包括互斥量[C/C++多线程编程之六]pthread互斥量,信号量[C/C++多线程编程之七]pthread信号量,互斥能很好的处理共享资源访问的协调问题,是多线程同步必不可少的机制.互斥机制也有其缺陷,当线程在等待共享资源满足某个条件,互斥机制下,必须不断地加锁和解锁,其间查询共享资源是否满足条件,这将带来巨大的消耗.         此时,需要新的机制来解决这个问题.      

转载自~浮云比翼:Step by Step:Linux C多线程编程入门(基本API及多线程的同步与互斥)

Step by Step:Linux C多线程编程入门(基本API及多线程的同步与互斥) 介绍:什么是线程,线程的优点是什么 线程在Unix系统下,通常被称为轻量级的进程,线程虽然不是进程,但却可以看作是Unix进程的表亲,同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等.但同一进程中的多个线程有各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage). 一

python多线程编程(2): 使用互斥锁同步线程

上一节的例子中,每个线程互相独立,相互之间没有任何关系.现在假设这样一个例子:有一个全局的计数num,每个线程获取这个全局的计数,根据num进行一些处理,然后将num加1.很容易写出这样的代码: # encoding: UTF-8import threadingimport time class MyThread(threading.Thread): def run(self): global num time.sleep(1) num = num+1 msg = self.name+' set