进程/线程同步——Critical Section,Mutex

电脑杂谈  发布时间:2020-02-16 22:00:42  来源:网络整理

ccriticalsection释放_ccriticalsection用法_c ccriticalsection

转自:

临界区(Critical Section)

保证在某一时刻只有一个线程可访问数据的简便方法。在任意时刻只允许一个线程对共享资源进行访问。如果有多个线程试图同时访问临界区,那么在有一个线程进 入后其它所有试图访问此临界区的轮询将被挂起,并经常大幅到处于临界区的线程离开。临界区在被释放后,其他线程可以再次抢占,并借此达到用原子形式操作共 享资源的目的。

临界区包含两个操作语义: EnterCriticalSection() 进入临界区 LeaveCriticalSection() 离开临界区

EnterCriticalSection()语句执行后代码将处于临界区以后无论出现哪些,必须保证与之匹配的 LeaveCriticalSection()都无法被执行到。否则临界区保护的共享资源将永远不会被传递。在使用临界区时,一般不允许其运行时间过长, 只要开启临界区的协程还没有离开,其他所有试图开启此临界区的线程都会被挂起而处于至等待状态,并会在必定程度上制约。程序的运行性能。尤其必须留意的是 不要将等待用户输入或是其它一些外界干预的操作包括到临界区。如果处于了临界区却始终没有释放,同样也会导致其它线程的长时间等待。换句话说,在执行了 EnterCriticalSection()语句进入临界区后无论出现哪些,必须保证与之匹配的LeaveCriticalSection()都无法被 执行至。可以借助添加结构化异常处理代码来保证LeaveCriticalSection()语句的执行。虽然临界区同步速率很快ccriticalsection释放,但仍没法用来同步本进 程内的线程,而不可用来同步多个进程中的线程。

MFC提供了这些用途完备的类,我用MFC实现了临界区。MFC为临界区提供有一个CCriticalSection类,使用该类进行线程同步处理是相当 简单的。只需在泛型变量中用CCriticalSection类成员函数Lock()和UnLock()标定出被保护代码片段即可。Lock()后代码用 到的资源自动被视为临界区内的资源被保护。UnLock后别的线程能够访问那些资源。

互斥量(Mutex)

互斥(Mutex)是一种用途十分广泛的内核对象。能够确保多个线程对同一共享资源的互斥访问。同临界区有些类似,只有拥有互斥对象的线程才具备访问资源 的权限,由于互斥对象只有一个,因此就决定了任何状况下此共享资源都不会同时被多个线程所访问。当前占据资源的线程在任务处理完后要将拥有的互斥对象交 出,以便其它线程在取得后得以访问资源。与其它几种内核对象不同,互斥对象在操作系统中拥有特殊代码,并由操作系统来管理,操作系统甚至还允许其进行一些 其他内核对象所不能进行的非常规操作。 互斥量跟临界区很相近,只有拥有互斥对象的线程才具备访问资源的权限,由于互斥对象只有一个,因此就决定了任何状况下此共享资源都不会同时被多个线程所访 问。当前占据资源的线程在任务处理完后要将拥有的互斥对象交出,以便其它线程在取得后得以访问资源。互斥量比临界区复杂。因为使用互斥不仅仅能够在同一要 用程序不同线程中推动资源的安全共享,而且可以在不同应用程序的线程之间推动对资源的安全共享。

以互斥内核对象来保持线程同步可能用到的变量主要有CreateMutex()、OpenMutex()、ReleaseMutex()、 WaitForSingleObject()和WaitForMultipleObjects()等。在使用互斥对象前,首先应借助 CreateMutex()或OpenMutex()创建或开启一个互斥对象。CreateMutex()函数原型为:

HANDLE CreateMutex(

LPSECURITY_ATTRIBUTES lpMutexAttributes, // 安全属性指针

BOOL bInitialOwner, // 初始拥有者

LPCTSTR lpName // 互斥对象名

);

参数bInitialOwner主要用来控制互斥对象的初始状况。一般多将其设定为 FALSE,以说明互斥对象在建立时并没有为任何线程所占有。如果在建立互斥对象时选定了对象名,那么可以在本进程其他地方或是在其它进程通过 OpenMutex()函数得到此互斥对象的线程。OpenMutex()函数原型为:

HANDLE OpenMutex(

DWORD dwDesiredAccess, // 访问标志

BOOL bInheritHandle, // 继承标志

LPCTSTR lpName // 互斥对象名

);

当现在对资源具备访问权的线程不再需要访问此资源而应离开时,必须借助ReleaseMutex()函数来释放其拥有的互斥对象,其变量原型为:

BOOL ReleaseMutex(HANDLE hMutex);

其唯一的参数hMutex为待释放的互斥对象赋值。至于 WaitForSingleObject()和WaitForMultipleObjects()等待函数在互斥对象保持线程同步中所起的作用与在其它内 核对象中的作用是基本一致的,也是等待互斥内核对象的通知。但是此处必须非常强调的是:在互斥对象通知引起调用等待函数返回时,等待函数的返回值不再是通 常的WAIT_OBJECT_0(对于WaitForSingleObject()函数)或是在WAIT_OBJECT_0到 WAIT_OBJECT_0+nCount-1之间的一个值(对于WaitForMultipleObjects()函数),而是将返回一个 WAIT_ABANDONED_0(对于WaitForSingleObject()函数)或是在WAIT_ABANDONED_0到 WAIT_ABANDONED_0+nCount-1之间的一个值(对于WaitForMultipleObjects()函数)。以此来说明线程正在等 待的互斥对象由另外一个线程所拥有,而此线程却在使用完共享资源前就早已终止。除此之外,使用互斥对象的方式在等待线程的可调度性上同使用其它几种内核对 象的方式也是所不同,其他内核对象在没有得到通知时,受调用等待函数的作用,线程将要挂起,同时失去可调度性,而使用互斥的方式仍可以在期待的同时却带有 可调度性,这也正是互斥对象所能完成的非常规操作之一。

在编写程序时,互斥对象多用在对这些为多个线程所访问的内存块的保护上,可以保证任何线程在处理此内存块时都对其拥有可靠的独占访问权。

互斥对象在MFC中借助CMutex类进行描述。使用CMutex类的方式十分简洁,在构造CMutex类对象的同时可以指明待查询的互斥对象的名字,在 构造函数返回后即可访问此互斥变量。CMutex类也是只带有构造函数这唯一的成员变量,当完成对互斥对象保护资源的访问后,可借助调用从父类 CSyncObject继承的UnLock()函数完成对互斥对象的释放。CMutex类构造函数原型为:

CMutex( BOOL bInitiallyOwn = FALSE, LPCTSTR lpszName = NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL );

c ccriticalsection_ccriticalsection用法_ccriticalsection释放

该类的适用范围跟实现原理与API方法建立的互斥内核对象是完全类似的,但应简单的多。

Mutex和Critical Section都是主要用于限制多线程(Multithread)对全局或共享的数组、对象或存储空间的访问。下面是其主要的优劣点(不同的地方用绿色表示)。

Critical Section

性能和速率

慢。

Mutex 是内核对象,相关变量的执行 (WaitForSingleObject,

ReleaseMutex)需要用户模式(User Mode)到内核方式(Kernel Mode)的转换,在x86处理器上这些转换通常应发费600个左右的 CPU指令周期。

快。

Critical Section本身不是内核对象,相关函数(EnterCriticalSection,LeaveCriticalSection)的调用一般都在用户体系内执行,在x86处理器上通常只应该发费9个左右的 CPU指令周期。只有当想要获取的锁正好被别的线程拥有时就会退化成跟Mutex一样,即转化到内核方式,发费600个左右的 CPU指令周期。

能否跨越进程(Process)边界

可以

不可

定义写法

HANDLE hmtx;

CRITICAL_SECTION cs;

初始化写法

hmtx= CreateMutex (NULL, FALSE, NULL);

InitializeCriticalSection(&cs);

结束清除写法

CloseHandle(hmtx);

DeleteCriticalSection(&cs);

无限期等待的写法

WaitForSingleObject (hmtx, INFINITE);

EnterCriticalSection(&cs);

0等待(状态检测)的写法

WaitForSingleObject (hmtx, 0);

TryEnterCriticalSection(&cs);

任意时间等待的写法

WaitForSingleObject (hmtx, dwMilliseconds);

不支持

锁释放的写法

ReleaseMutex(hmtx);

LeaveCriticalSection(&cs);

能否被一道用于期待其他内核对象

可以(使用WaitForMultipleObjects,WaitForMultipleObjectsEx,MsgWaitForMultipleObjects,MsgWaitForMultipleObjectsEx等等)

不可

当拥有锁的线程死亡时

Mutex变成abandoned状态,其他的期待线程可以获得锁。

Critical Section的状态不可知(undefined),以后的动作就不能保证了。

自己会不会锁住自己

不会(对未获取的Mutex,重复读取WaitForSingleObject不会锁住自己。但最终你别忘了应调用同样次数的ReleaseMutex)

不会(对未获取的Critical Section,重复读取EnterCriticalSection不会锁住自己。但最终你别忘了应调用同样数量的LeaveCriticalSection)

信号量(Semaphores)

信号量对象对泛型的同步模式与上面几种方法不同,信号允许多个线程同时使用共享资源,这与操作系统中的PV操作相同。它强调了同时访问共享资源的线程最大 数目。它允许多个线程在同一时刻访问同一资源,但是必须限制在同一时刻访问此资源的最大线程数量。在用CreateSemaphore()创建信号量时即 要同时强调允许的最大资源计数和当前可用资源计数。一般是将当前可用资源计数设置为最大资源计数,每下降一个线程对共享资源的访问,当前可用资源计数就会 减1,只要当前可用资源计数是小于0的,就可以发出信号量信号。但是当前可用计数减少至0时则表明当前占用资源的线程数尚未超过了所允许的最大数目,不能 在容许其它线程的处于,此时的信号量信号将能够发出。线程在处理完共享资源后,应在离开的同时借助ReleaseSemaphore()函数将当前可用资 源计数加1。在任何之后当前可用资源计数决不可能小于最大资源计数。 信号量是通过计数来对线程访问资源进行控制的,而实际上信号量确实也被叫做Dijkstra计数器。

PV操作及信号量的概念都是由德国科学家E.W.Dijkstra提出的。信号量S是一个整数,S大于等于零时代表能供并发进程使用的资源实体数,但S大于零时则表示正在期待使用共享资源的进程数。

P操作申请资源:

(1)S减1;

(2)若S减1后却高于等于零,则进程再次执行;

(3)若S减1后大于零,则该进程被阻塞后转入与该信号相对应的队列中,然后进入进程调度。

V操作 释放资源:

(1)S加1;

(2)若相加结果小于零,则进程再次执行;


本文来自电脑杂谈,转载请注明本文网址:
https://www.pc-fly.com/a/jisuanjixue/article-141012-1.html

相关阅读
    发表评论  请自觉遵守互联网相关的政策法规,严禁发布、暴力、反动的言论

    • 明元帝
      明元帝

      从去年开始不再喝了

    热点图片
    拼命载入中...