临界区,互斥量,信号量,事件的区别

原创
小哥 2年前 (2023-05-24) 阅读数 32 #大杂烩

转自:http://blog.csdn.net/wuyuan2011woaini/article/details/7564842

临界 区(Critical section)与 互斥 体(Mutex)的区别

1关键部分只能用于同一进程中线程之间的互斥访问;互斥可用于对象进程或线程之间的互斥访问。

2关键部分是非内核对象。它只在用户模式下执行锁定操作,速度很快;互斥体是在内核状态下执行锁定操作的内核对象,速度很慢。

3、关键部分和互斥锁Windows可在所有平台上使用;Linux下面只有互斥锁可用。

关键部分、互斥锁、信号量和事件之间的区别

同步和互斥进程或线程的四种控制方法
1、临界区:通过多个线程的序列化访问公共资源或一段代码速度快,适合控制数据访问。
2、互斥量:旨在协调对共享资源的个人访问。
3、信号量:旨在控制有限数量的用户资源。
4、事 件:用于通知线程发生了某些事件,从而启动后续任务的启动。

临界区(Critical Section)

一种确保在特定时间只有一个线程可以访问数据的简单方法。任何时候只允许一个线程访问共享资源。如果多个线程尝试同时访问关键部分,则 在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到输入关键部分的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操 为了共享资源。
关键部分包含两个操作基元:
EnterCriticalSection() 输入关键部分
LeaveCriticalSection() 离开关键部分
EnterCriticalSection() 语句执行后代码将输入关键部分以后无论发生什么,必须确保与之匹配的 LeaveCriticalSection() 都可以执行。否则,受关键部分保护的共享资源将永远不会释放。虽然关键部分的同步速度非常快,但只能用于同步 进程中的线程,不能用于同步多个进程中的线程。
MFC提供了许多功能齐全的类,我使用MFC实现了关键部分。MFC为临界区提供有一个 CCriticalSection类,用于线程同步处理 这很简单。只需使用CCriticalSection类成员函数Lock()和UnLock() 只需校准受保护的代码片段。Lock()后代 代码使用的资源自动被视为关键部分中的资源受到保护。UnLock只有其他线程可以访问这些资源。

互斥量(Mutex)
互斥锁与关键部分非常相似。只有具有互斥对象的线程才能访问资源。由于只有一个互斥对象,因此它确定在任何情况下都不会同时被多个线程访问此共享资源。当前占用资源线程应在任务处理后交出其拥有的互斥对象,以便其他线程在获取资源后可以访问该资源。互斥锁比关键部分更复杂。因为使用互斥不仅可以在同一应用程序中的不同线程之间安全共享资源,还可以在不同应用程序中的线程之间安全共享资源。

互斥锁包含多个操作基元:
CreateMutex() 创建互斥锁
OpenMutex() 打开互斥锁
ReleaseMutex() 释放互斥锁
WaitForMultipleObjects() 等待互斥对象

同样MFC有一个CMutex类。使用CMutex互斥体操作的类实现非常简单,但应特别注意CMutex调用构造函数
CMutex( BOOL bInitiallyOwn = FALSE, LPCTSTR lpszName = NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL)
未使用的参数不能乱填,乱填可能会导致意外的运行结果。

信号量(Semaphores)

信号量对象与线程的同步方法与以前的方法不同,因为信号量允许多个线程同时使用共享资源 这与PV操作是相同的。它表示同时访问共享 资源线程 最大数量。它允许多个线程同时访问同一资源,但需要限制可以同时访问此资源的最大线程数。使用中CreateSemaphore() 创建信号量 此时,需要指示允许的最大资源计数和当前可用资源计数。通常,当前可用资源计数设置为最大资源计数,对于访问共享资源的每个附加线程,当前可用资源计数 就会减1只要当前可用资源计数大于0您可以发出信号量信号。但当前可用计数已减少到0当,它指示当前占用资源线程数已达到允许的最大数量, 不允许其他线程进入,此时不会发出信号量信号。处理共享资源后,线程应传递ReleaseSemaphore() 函数目前能够 使用资源计数添加1。当前可用资源计数任何时候都不能超过最大资源计数。
PV操作和信号量的概念是由荷兰科学家开发的E.W.Dijkstra提出。信号S它是一个整数,S当大于或等于零时,它表示可供并发进程使用的资源实体数,但S当小于零时,它表示等待使用共享资源的进程数。
P操作 请求资源:
(1)S减1;
(2)若S减1如果仍大于或等于零,则进程继续执行;
(3)若S减1如果后者小于零,则进程被阻塞并进入信号对应的队列,然后进入进程调度。
V操作 发布资源:
(1)S加1;
(2)如果总和结果大于零,则进程继续执行;
(3如果总和结果小于等于零,则从信号的等待队列中唤醒等待进程,然后返回到原始进程进一步执行或转移到进程调度。

信号量包含多个操作基元:
CreateSemaphore() 创建信号量
OpenSemaphore() 打开信号量
ReleaseSemaphore() 释放信号量
WaitForSingleObject() 等待信号量

事件(Event)
事件对象还可以通过通知操作维护线程同步。并且可以实现不同进程中的线程同步操作。
信号量包含多个操作基元:
CreateEvent() 创建一个事件
OpenEvent() 打开活动
SetEvent() 回置事件
WaitForSingleObject() 等待事件
WaitForMultipleObjects() 等待多个事件
WaitForMultipleObjects 功能原型:
WaitForMultipleObjects(
IN DWORD nCount, // 等待手柄数
IN CONST HANDLE *lpHandles, //指向句柄数组
IN BOOL bWaitAll, //是否完全等待国旗
IN DWORD dwMilliseconds //等待时间

参 数nCount指定要等待的内核对象数,存储这些内核对象的数组由lpHandles来指向。fWaitAll对于指定的nCount指定了内核对象的两种等待方法,即TRUE该函数仅在通知所有对象时返回FALSE只要他们中的任何一个收到通知,他们就可以返回。 dwMilliseconds这里和在WaitForSingleObject() 的作用是完全一致的。如果等待超时,函数将返回 WAIT_TIMEOUT。

总结:
1. 互斥体与关键部分非常相似,但它们可以命名,也就是说,它们可以跨进程使用。因此,创建互斥锁需要更多的资源。因此,仅将关键部分用于内部使用将带来速度优势并减少资源消耗 。因为互斥锁是跨进程互斥锁,一旦创建就可以按名称打开。
2. 互斥量(Mutex)信号灯(Semaphore)、事件(Event)两者都可以由跨进程用于同步数据操作,而其他对象独立于数据同步操作。但是,对于进程和线程,如果它们正在运行,则它们处于未签名状态,退出后,它们处于信号状态。所以你可以使用WaitForSingleObject等待该过程和 线程已退出。
3. 通过使用互斥锁,可以将资源指定为独占使用,但在以下情况之一中,互斥锁无法处理它。例如,如果用户购买了具有三个并发访问许可证的数据库系统,则可以根据用户购买的访问许可证数量来确定线程数/进程可以同时执行数据库操作,此时,如果使用互斥锁,则无法达到此要求。信号量对象可以说是一个资源计数器。

有关更详细的介绍,请看这里:

http:// blog.csdn.net/wuyuan2011woaini/article/details/7564825

事件对象:
事件对象还属于内核对象,并包含一个计数器、一个布尔值(指示事件是自动重置事件还是手动重置事件)和一个布尔值(指示事件是处于通知状态还是未通知状态)。

两种事件对象:
1.手动重置的事件。   通知手动重置事件时,等待该事件的所有线程都是可调度线程。
2.自动重置的事件。   通知自动重置事件时,只有一个等待该事件的线程成为可传输线程。

关键代码片段:
关键代码段(关键部分)在用户模式下工作。

关键代码段(关键部分)是一个小型代码段,在执行代码之前必须对某些资源具有独占访问权限。

线程死锁:
线程1具有关键节对象A,等待关键部分对象B所有权, 线程2具有关键节对象B,等待关键部分对象A结果的所有权是死锁。

互斥对象、事件对象和关键代码段的比较
互斥锁和事件对象都属于内核对象,使用内核对象进行线程同步的速度较慢。但是,使用内核对象(如互斥锁和事件对象)允许在多个进程中的线程之间进行同步。

关键代码片段在用户模式下工作,同步速度快。但是,使用密钥代码片段时,很容易进入死锁状态,因为在等待输入密钥代码片段时无法设置超时值。

VC线程同步的三种方法(互斥、事件、关键部分)

首选使用关键部分对象,主要原因是它易于使用。
EnterCriticalSection() 函数等待指定危险部分对象的所有权。当允许调用线程所有权时,函数将返回。
EnterCriticalSection (),单独进程的线程可以使用危险节对象作为相互-排除同步。 该进程负责分配危险节对象使用的内存, 它声明了一个CRITICAL_SECTION类型 变量实现。在使用危险部分之前,进程的某些线程必须调用 InitializeCriticalSection 函数设置对象的初始值.
确保对共享资源的互斥访问,每个线程调用EnterCriticalSection 或者 TryEnterCriticalSection 在执行访问受保护资源的任何代码片段之前请求危险部分所有权的函数。

01

#include <windows.h>

02

#include <iostream>

03

using namespace std;

04

DWORD WINAPI Fun1Proc( LPVOID lpParameter);

05

DWORD WINAPI Fun2Proc( LPVOID lpParameter);

06

int tickets=100;

07

CRITICAL_SECTION g_csA;

08

CRITICAL_SECTION g_csB;

09

void main()

10

{

11

HANDLE hThread1;

12

HANDLE hThread2;

13

hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);

14

hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);

15

CloseHandle(hThread1);

16

CloseHandle(hThread2);

17

InitializeCriticalSection(&g_csA);

18

InitializeCriticalSection(&g_csB);

19

Sleep(40000);

20

DeleteCriticalSection(&g_csA);

21

DeleteCriticalSection(&g_csB);

22

}

23

DWORD WINAPI Fun1Proc( LPVOID lpParameter)

24

{

25

while (TRUE)

26

{

27

EnterCriticalSection(&g_csA);

28

Sleep(1);

29

//EnterCriticalSection(&g_csB);//关键部分的同步和联锁

30

if (tickets>0)

31

{

32

Sleep(1);

33

cout<< "Thread1 sell ticket :" <<tickets--<<endl;

34

//LeaveCriticalSection(&g_csB);

35

LeaveCriticalSection(&g_csA);

36

}

37

else

38

{

39

//LeaveCriticalSection(&g_csB);

40

LeaveCriticalSection(&g_csA);

41

break ;

42

}

43

}

44

return 0;

45

}

46

DWORD WINAPI Fun2Proc( LPVOID lpParameter)

47

{

48

while (TRUE)

49

{

50

EnterCriticalSection(&g_csB);

51

Sleep(1);

52

EnterCriticalSection(&g_csA);

53

if (tickets>0)

54

{

55

Sleep(1);

56

cout<< "Thread2 sell ticket :" <<tickets--<<endl;

57

LeaveCriticalSection(&g_csA);

58

LeaveCriticalSection(&g_csB);

59

}

60

else

61

{

62

LeaveCriticalSection(&g_csA);

63

LeaveCriticalSection(&g_csB);

64

break ;

65

}

66

}

67

return 0;

68

}


二、使用互斥对象
DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds);
如果时间发出信号并且状态返回WAIT_OBJECT_0,如果时间超过dwMilliseconds值但时间事件仍然没有信号状态,返回WAIT_TIMEOUT
WaitForSingleObject用于检测的功能hHandle当函数的执行时间超过dwMilliseconds它返回,但如果参数dwMilliseconds为INFINITE时函数将直到相应时间事件变成有信号状态才返回,否则就一直等待下去,直到WaitForSingleObject以下代码仅在有返回时执行。

01

#include <windows.h>

02

#include <iostream>

03

using namespace std;

04

DWORD WINAPI Fun1Proc( LPVOID lpParameter);

05

DWORD WINAPI Fun2Proc( LPVOID lpParameter);

06

int index =0;

07

int tickets=100;

08

HANDLE hMutex;

09

void main()

10

{

11

HANDLE hThread1;

12

HANDLE hThread2;

13

//创建线程

14

hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);

15

hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);

16

CloseHandle(hThread1);

17

CloseHandle(hThread2);

18

//**************************************************************

19

//确保只有一个应用程序实例运行,创建命名互斥对象.

20

hMutex=CreateMutex(NULL,TRUE, LPCTSTR ( "tickets" ));

21

//创建时,主线程拥有互斥对象,互斥对象的线程ID主线程ID,同时将互斥锁的内部计数器设置为1

22

if (hMutex)

23

{

24

if (ERROR_ALREADY_EXISTS==GetLastError())

25

{

26

cout<< "only one instance can run!" <<endl;

27

//Sleep(40000);

28

return ;

29

}

30

}

31

//**************************************************************

32

WaitForSingleObject(hMutex,INFINITE);

33

//使用此函数请求互斥对象时,虽然物体处于无信号状态,但是因为请求的线程ID拥有互斥对象的线程ID是相同的.所以你仍然可以请求这个互斥锁,所以添加了互斥锁的内部计数器1,内部计数器的值为2. 这意味着有两个等待操作

34

ReleaseMutex(hMutex); //释放一次互斥锁,此互斥锁的内部计数器的值减小1,操作系统不会将此互斥锁更改为通知状态.

35

ReleaseMutex(hMutex); //释放一次互斥锁,该互斥对象内部计数器的值为0,同时将对象设置为通知状态.

36

//对于互斥对象,谁拥有谁发布

37

Sleep(40000);

38

}

39

DWORD WINAPI Fun1Proc( LPVOID lpParameter)

40

{

41

while (TRUE)

42

{

43

WaitForSingleObject(hMutex,INFINITE); //等待来自互斥锁的信号

44

if (tickets>0)

45

{

46

Sleep(1);

47

cout<< "thread1 sell ticket :" <<tickets--<<endl;

48

}

49

else

50

break ;

51

ReleaseMutex(hMutex); //为此互斥对象设置线程ID为0,并将对象设置为信号状态

52

}

53

return 0;

54

}

55

DWORD WINAPI Fun2Proc( LPVOID lpParameter)

56

{

57

while (TRUE)

58

{

59

WaitForSingleObject(hMutex,INFINITE);

60

if (tickets>0)

61

{

62

Sleep(1);

63

cout<< "thread2 sell ticket :" <<tickets--<<endl;

64

}

65

else

66

break ;

67

ReleaseMutex(hMutex);

68

}

69

return 0;

70

}


三、使用事件对象

1

HANDLE CreateEvent(

2

LPSECURITY_ATTRIBUTES     lpEventAttributes, //     SD

3

BOOL bManualReset, //     reset     type

4

BOOL bInitialState, //     initial     state

5

LPCTSTR lpName //     object     name

6

);

此函数创建一个Event同步对象,并返回对象的Handle

lpEventAttributes     一般为NULL
bManualReset               创建的Event是自动复位还是手动复位     ,如果true,人工复位,
一旦该Event设置为有信号,然后它会等到ResetEvent()API仅在调用时还原
为无信号.     如果为false,Event设置为有信号,当有一个wait到它的Thread时,
该Event将自动重置,变成无信号.
bInitialState             初始状态,true,有信号,false无信号
lpName                           Event对象名

一个Event创建后,可以用OpenEvent()API要获得其Handle,用CloseHandle()
来关闭它,用SetEvent()或PulseEvent()将其设置为具有信号,用ResetEvent()
使其无信号,用WaitForSingleObject()或WaitForMultipleObjects()来等待
它成为信号.

PulseEvent()这是一种非常有趣的使用方法,正如这个API的名字,它使一个Event
物体的状态经历脉冲变化,从无信号到信号再到无信号,整个操作是原子的.
用于自动复位Event对象,它只发布第一个thread(如果有),而对于
手动重置Event对象,它释放所有等待thread.

显示代码

打印

01

#include <windows.h>

02

#include <iostream>

03

using namespace std;

04

DWORD WINAPI Fun1Proc( LPVOID lpParameter);

05

DWORD WINAPI Fun2Proc( LPVOID lpParameter);

06

int tickets=100;

07

HANDLE g_hEvent;

08

void main()

09

{

10

HANDLE hThread1;

11

HANDLE hThread2;

12

//**************************************************

13

//创建命名的自动重置事件内核对象

14

g_hEvent=CreateEvent(NULL,FALSE,FALSE, LPCTSTR ( "tickets" ));

15

if (g_hEvent)

16

{

17

if (ERROR_ALREADY_EXISTS==GetLastError())

18

{

19

cout<< "only one instance can run!" <<endl;

20

return ;

21

}

22

}

23

//**************************************************

24

SetEvent(g_hEvent);

25

//创建线程

26

hThread1=CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);

27

hThread2=CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);

28

Sleep(40000);

29

//关闭事件对象句柄

30

CloseHandle(g_hEvent);

31

}

32

DWORD WINAPI Fun1Proc( LPVOID lpParameter)

33

{

34

while (TRUE)

35

{

36

WaitForSingleObject(g_hEvent,INFINITE);

37

//ResetEvent(g_hEvent);

38

if (tickets>0)

39

{

40

Sleep(1);

41

cout<< "thread1 sell ticket :" <<tickets--<<endl;

42

SetEvent(g_hEvent);

43

}

44

else

45

{

46

SetEvent(g_hEvent);

47

break ;

48

}

49

}

50

return 0;

51

}

52

DWORD WINAPI Fun2Proc( LPVOID lpParameter)

53

{

54

while (TRUE)

55

{

56

WaitForSingleObject(g_hEvent,INFINITE);

57

//ResetEvent(g_hEvent);

58

if (tickets>0)

59

{

60

cout<< "Thread2 sell ticket :" <<tickets--<<endl;

61

SetEvent(g_hEvent);

62

}

63

else

64

{

65

SetEvent(g_hEvent);

66

break ;

67

}

68

}

69

return 0;

版权声明

所有资源都来源于爬虫采集,如有侵权请联系我们,我们将立即删除

热门