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

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

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

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

(1)关中断

(2)数学互斥方法

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

(4)cpu原子操作

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

(1)关中断

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

[cpp] view plaincopy

  1. #include <stdio.h>
  2. int main()
  3. {
  4. __asm{
  5. cli
  6. sti
  7. }
  8. return 1;
  9. }

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

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

(2)数学方法

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

[cpp] view plaincopy

  1. unsigned int flag[2] = {0};
  2. unsigned int turn = 0;
  3. void process(unsigned int index)
  4. {
  5. flag[index] = 1;
  6. turn =  1 - index;
  7. while(flag[1 - index] && (turn == (1 - index)));
  8. do_something();
  9. flag[index] = 0;
  10. }

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

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

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

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

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

系统加锁过程,

[cpp] view plaincopy

  1. void Lock(HANDLE hLock)
  2. {
  3. __asm {cli};
  4. while(1){
  5. if(/* 锁可用*/){
  6. /* 设定标志,表明当前锁已被占用 */
  7. __asm {sti};
  8. return;
  9. }
  10. __asm{sti};
  11. schedule();
  12. __asm{cli};
  13. }
  14. }

系统解锁过程,

[cpp] view plaincopy

  1. void UnLock(HANDLE hLock)
  2. {
  3. __asm {cli};
  4. /* 设定标志, 当前锁可用 */
  5. __asm{sti};
  6. }

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

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

[cpp] view plaincopy

  1. InterLockedAdd
  2. InterLockedExchange
  3. InterLockedCompareExchange
  4. InterLockedIncrement
  5. InterLockedDecrement
  6. InterLockedAnd
  7. InterLockedOr

这一篇博文没怎么看明白,对操作系统还不是很熟,要好好学操作系统啊

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

时间: 2024-10-12 02:42:21

多线程的那点事儿(之数据互斥)的相关文章

C++拾遗--多线程:临界区解决子线程的互斥

C++拾遗--多线程:临界区解决子线程的互斥 前言 为了解决子线程的互斥问题,windows系统提出了关键段或临界区(CRITICAL_SECTION)的概念.它一共有四个共两对操作:初始化.销毁,进入.离开.它们定义在头文件synchapi.h中. 1.初始化变量 VOID WINAPI InitializeCriticalSection( LPCRITICAL_SECTION lpCriticalSection ); 2.销毁变量 VOID WINAPI DeleteCriticalSect

多线程更新已排序的Datagridview数据,造成数据错位

多线程更新已排序的Datagridview数据,触发Datagridview的auto-sort时间,数据重新排序,造成后面更新数据的更新错误. 解决方法: 方法一.设置Datagridview的表头属性,DataGridViewColumnSortMode 1. 如果已经有默认的排序表头DataGridView.SortedColumn, 则 : DataGridViewColumn column = dgv.SortedColumn; //默认的排序列 column.SortMode = D

c#多线程更新窗口(winform)GUI的数据

1. 在.net framwork 2.0中,可以通过以下代码来实现: 1 2 3 4 5 6 7 8 9 10 11 12 private delegate void SetControlPropertyThreadSafeDelegate(Control control, string propertyName, object propertyValue); public static void SetControlPropertyThreadSafe(Control control, st

多线程-生产者与消费者(存储数据与打印数据)

无线程同步: 存储数据: 1 public class Storage { 2 int data; 3 4 public int getData() { 5 return data; 6 } 7 8 public void setData(int data) { 9 this.data = data; 10 } 11 12 } 产生数据: 1 public class Counter implements Runnable { 2 private Storage storage; 3 4 pub

Delphi多线程编程之同步读写全局数据

开始研究最重要的多线程读写全局数据了,结合书上的例子,我修改成下面的情况: unit Tst_Thread3U; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,Dialogs, StdCtrls; type   TForm1 = class(TForm)     Button1: TButton;     Memo1: TMemo;     Button2: TBu

windows下多线程同步(利用事件对象,互斥对象,关键代码段)实现

一:利用事件实现线程同步 1.createthread函数的用法 hThread = CreateThread(&security_attributes, dwStackSize, ThreadProc,pParam, dwFlags, &idThread) ; HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, DWORD dwStackSize, LPTHREAD_START_ROUTINE lpStartAdd

Java基础加强之多线程篇(线程创建与终止、互斥、通信、本地变量)

线程创建与终止 线程创建 Thread类与Runnable接口的关系 public interface Runnable { public abstract void run(); } public class Thread implements Runnable { /* What will be run. */ private Runnable target; ...... /** * Causes this thread to begin execution; the Java Virtu

转--- 秒杀多线程第七篇 经典线程同步 互斥量Mutex

阅读本篇之前推荐阅读以下姊妹篇: <秒杀多线程第四篇一个经典的多线程同步问题> <秒杀多线程第五篇经典线程同步关键段CS> <秒杀多线程第六篇经典线程同步事件Event> 前面介绍了关键段CS.事件Event在经典线程同步问题中的使用.本篇介绍用互斥量Mutex来解决这个问题. 互斥量也是一个内核对象,它用来确保一个线程独占一个资源的访问.互斥量与关键段的行为非常相似,并且互斥量可以用于不同进程中的线程互斥访问资源.使用互斥量Mutex主要将用到四个函数.下面是这些函数

秒杀多线程第七篇 经典线程同步 互斥量Mutex

阅读本篇之前推荐阅读以下姊妹篇: <秒杀多线程第四篇一个经典的多线程同步问题> <秒杀多线程第五篇经典线程同步关键段CS> <秒杀多线程第六篇经典线程同步事件Event> 前面介绍了关键段CS.事件Event在经典线程同步问题中的使用.本篇介绍用互斥量Mutex来解决这个问题. 互斥量也是一个内核对象,它用来确保一个线程独占一个资源的访问.互斥量与关键段的行为非常相似,并且互斥量可以用于不同进程中的线程互斥访问资源.使用互斥量Mutex主要将用到四个函数.下面是这些函数