【多线程】临界区,互斥量,信号量,条件变量,事件以及区别
目录
互斥量(互斥锁)和信号量(PV)
多任务间同步的方式包括关中断,调度器上锁, 互斥量(mutex),信号量,消息队列几种, 其中互斥量和信号量使用使用方式相近,连概念和实现都有些近似, 所以没有扎实操作系统背景知识的同学常常分不清出他们的区别,容易混淆, 下面简单分析以下.
1.互斥量(简化的信号量,只有两种状态:0和1,有所有者的概念)----资源冲突
互斥量其实可以理解为一个简化的信号量,它只有两种状态:0和1,互相斥量是管理临界资源的一种有效手段, 因为互斥量是独占的, 所以在一个时刻只允许一个线程占有互斥量,利用这个性质来实现共享资源的互斥量保护,任何时刻只允许一个线程获得互斥量对象,未能够获得互斥量对象的线程被挂起在该互斥量的等待线程队列上,这一点和1资源信号量是相同的, 但互斥量有所有者的概念,高优先级的任务可以在获取互斥量时通过对比所有者的优先级是否高于自己来决定是否提升所有者的优先级。 所以互斥量可以有效对付优先级反转的问题。
互斥锁都是二元,信号量可以是多元(表示资源数,比如队列中每增加一个成员,信号量加1,表示队列可取成员数+1,即PV操作)
2.信号量(不能解决优先级反转问题)----线程调度
信号量是用来解决线程同步和互斥的通用工具, 和互斥量类似, 信号量也可以用作于资源互斥访问, 但信号量没有所有者的概念,在应用上比互斥量更广泛,信号量比较简单, 不能解决优先级反转问题,但信号量是一种轻量级的对象,比互斥量小巧,灵活,因此在很多对互斥要求不严格的的系统中,经常使用信号量来管理互斥资源。
小结:
信号量不一定是锁定某一个资源,而是流程上的概念,而线程互斥量则是“锁住某一资源”的概念,在锁定期间内,其他线程无法对被保护的数据进行操作。在有些情况下两者可以互换。
mutex干的活儿和semaphore干的活儿不要混起来。
在这里,我模拟一个最典型的使用semaphore的场景:a源自一个线程,b源自另一个线程,计算c = a + b也是一个线程。(即一共三个线程)显然,第三个线程必须等第一、二个线程执行完毕它才能执行。在这个时候,我们就需要调度线程了:让第一、二个线程执行完毕后,再执行第三个线程。此时,就需要用semaphore了。
int a, b, c;
void geta()
{
a = calculatea();
semaphore_increase();
}
void getb()
{
b = calculateb();
semaphore_increase();
}
void getc()
{
semaphore_decrease();
semaphore_decrease();
c = a + b;
}
t1 = thread_create(geta);
t2 = thread_create(getb);
t3 = thread_create(getc);
thread_join(t3);
// semaphore的机制我在这里就不讲了,百度一下你就知道。
// semaphore_increase对应sem_post
// semaphore_decrease对应sem_wait
简而言之,锁是服务于共享资源的;而semaphore是服务于多个线程间的执行的逻辑顺序的。
作者:MononokeHime
链接:https://www.jianshu.com/p/c6ba8bcc22bc
两者之间的区别: 作用域 上锁时 信号灯的应用除了灯亮/灯灭这种二元灯以外,也可以采用大于1的灯数,以表示资源数大于1,这时可以称之为多元灯。 条件变量和互斥锁都是二元(0/1),信号量可以是多元(表示资源数,比如队列中每增加一个成员,信号量加1,表示队列可取成员数+1) |
关于所有者的概念,其实我也不是很清楚,只是最近看了一下FreeRTOS 信号量头文件的一些注释, 有些感悟和收获, 原文在 freertoscode/source/include/semphr.h中。
* This type of semaphore can be used for pure synchronisation between tasks or
* between an interrupt and a task. The semaphore need not be given back once
* obtained, so one task/interrupt can continuously 'give' the semaphore while
* another continuously 'takes' the semaphore. For this reason this type of
* semaphore does not use a priority inheritance mechanism. For an alternative
* that does use priority inheritance see xSemaphoreCreateMutex().
*
大概意思是说, sem和mutex的使用方式不同, 对于同一个上下文内来说,mutex的获取和释放必须成对调用, 获取次数和释放次数必须一样,强调所有权的概念。
sem则没有这些要求,可以一个上下文执行释放,另一个上下文执行获取, 强调的是资源数的概念。
原文:https://blog.csdn.net/tugouxp/article/details/68951576
信号和信号量
两者没有一点联系,是两个完全不同的东东,从实现机制、到作用,一点也没有联系
信号可以看作是中断的一种软件模拟,他提供了一种处理异步事件的方法,事实上,信号属于IPC的一种,也是在IPC当中唯一的异步驱动的实现方式;
信号可以分为可靠信号和非可靠信号,软件信号和硬件信号等。
和信号并列的技术有消息队列,共享内存,管道,知名管道,socket等,这都是IPC的方法;
信号量是用软件方法实现进程间访问资源同步的一种方法,
和信号量并列的技术有临界区,记录锁等,都是进程同步的方法。
信号量与条件变量
条件变量和互斥锁都是二元,信号量可以是多元(表示资源数,比如队列中每增加一个成员,信号量加1,表示队列可取成员数+1)
条件变量是另外一种同步机制,可以用于线程和管程中的进程互斥。通常与互斥量一起使用。条件变量允许线程由于一些暂时没有达到的条件而阻塞。
临界区与互斥量
2018-11-12 20:24:07
Win32 中关于进程和线程的协调工作是由同步机制来完成的,同步机制相当于线程间的红绿灯。
一. 同步和异步
举个例子:
PostMessage(),是把消息放到对方的消息队列中,然后不管三七二十一,就回到原调用点继续执行,这就是异步。
SendMessage(),就像调用一般性函数,直到调用的函数结束,才会回到原点,这就是同步行为。
二. Critical Sections
如果一个线程已经进入某个临界区,则另一个线程就绝不能够进入同一个临界区。
//初始化一个临界区
VOID InitializeCriticalSection(
LPCRITICAL_SECTION lpCriticalSection // critical section
);
//消除一个临界区
VOID DeleteCriticalSection(
LPCRITICAL_SECTION lpCriticalSection // critical section
);
//进入临界区
VOID EnterCriticalSection(
LPCRITICAL_SECTION lpCriticalSection // critical section
);
//离开临界区
VOID LeaveCriticalSection(
LPCRITICAL_SECTION lpCriticalSection // critical section
);
例如:
CRITICAL_SECTION gCriticalSection;
void Function()
{
InitializeCriticalSection(&gCriticalSection);
EnterCriticalSection(&gCriticalSection);
//Do something here
LeaveCriticalSection(&gCriticalSection);
DeleteCriticalSection(&gCriticalSection);
}
一旦线程进入一个临界区,则它就可以一再的重复进入该临界区,当然每个进入操作都必须对应离开操作。
也就是EnterCriticalSection( ),可以嵌套。
但是千万不要在临界区中调用 sleep(),或任何 Wait..() 函数。
临界区的缺点是:没有办法知道进入临界区中的那个线程是生是死。如果那个线程在进入临界区后当掉了,而且没有退出来,那么系统就没有办法消除掉此临界区。
三. Mutexes
Mutexes 用途和 Critical Section 非常类似,线程拥有 mutex 就好象线程进入 critical section 一样,但是它牺牲速度以增加弹性。
一旦没有任何线程拥有那个 mutex,这个 mutex 便处于激发状态
它与临界区的区别是:
1. Mutexes 操作要比 Critical Section 费时的多。
2. Mutexes 可以跨进程使用,Critical Section 则只能在同一进程中使用。
3. 等待一个 Mutex 时,你可以指定"结束等待"的时间长度,而 Critical Section 则不行。
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes, // 安全属性,默认为NULL
BOOL bInitialOwner, // initial owner
LPCTSTR lpName // mutex 的名称,是一个字符串
);
//返回值:如果成功返回 handle,否则返回 NULL
HANDLE OpenMutex(
DWORD dwDesiredAccess, // access
BOOL bInheritHandle, // inheritance option
LPCTSTR lpName // object name
);
//打开一个已经存在的 mutex
BOOL ReleaseMutex(
HANDLE hMutex // handle to mutex
);
//调用过程如下:
CreateMutex(); //创建
WaitForXXXObject(); //等待
ReleaseMutex(); //释放
CloseHandle(); //关闭
说明:
1. Mutex 的拥有权:
Mutex 的拥有权并非属于那个产生它的线程,而是那个最后对些 Mutex 进行 WaitXXX() 操作并且尚未进行 ReleaseMutex() 操作的线程。
2. Mutex 被舍弃:
如果线程在结束前没有调用 ReleaseMutex(),比如线程调用了 EXitThread() 或者因为当掉而结束。这时的 mutex 不会被摧毁,而是被视为"未被拥有"以及"未被激发"的状态,在下一个 WaitXXX() 中线程会被以WAIT_ABANDONED_0 (WAIT_ABANDONED_0_n + 1 )来通知。
3. 最初拥有者:
CreateMutex(),第二个参数 bInitialOwner,允许你指定现行线程是否立刻拥有产生出来的 mutex。
如果没有指定立刻拥有的情况:
HANDLE hMutex = CreateMutex(NULL, FALSE, "Sample Name");
int result = WaitForSingleObject(hMutex, INFINITE);
可能发生,在 CreateMutex 完成之后,发生了 context switch,执行权切换到另一个线程,那么其它进程就有可能在 mutex 的产生者调用 WaitForSingleObject( ) 之前,锁住这个 mutex 对象。
原文:https://blog.csdn.net/lwbeyond/article/details/7617234
总结
这里,主要是简单总结一下这几种同步量的用法。
1、互斥锁只用在同一个线程中,用来给一个需要对临界区进行读写的操作加锁。
2、信号量与互斥量不同的地方在于,信号量一般用在多个进程或者线程中,分别执行P/V操作。
3、条件变量一般和互斥锁同时使用,或者用在管程中。
4、互斥锁,条件变量都只用于同一个进程的各线程间,而信号量(有名信号量)可用于不同进程间的同步。当信号量用于进程间同步时,要求信号量建立在共享内存区。
5、互斥锁是为上锁而优化的;条件变量是为等待而优化的; 信号量既可用于上锁,也可用于等待,因此会有更多的开销和更高的复杂性。
Linux和windows临界区
2015-09-05 14:57:04
转自:http://blog.csdn.net/jernymy/article/details/6988876
1.声明
#ifdef _LINUX
pthread_mutex_t mutex_lock;
#endif
#ifdef WIN32
CRITICAL_SECTION mutex_lock;
#endif
2.初始化
#ifdef _LINUX
pthread_mutex_init(&mutex_lock, NULL);
#endif
#ifdef WIN32
InitializeCriticalSection(&mutex_lock);
#endif
3.进入和退出临界区
#ifdef _LINUX
thread_mutex_lock(&mutex_lock);
......
pthread_mutex_unlock(&mutex_lock);
#endif
#ifdef _WIN32
EnterCriticalSection(&mutex_lock);
....
LeaveCriticalSection(&mutex_lock);
#endif
4.删除临界区
#ifdef _LINUX
pthread_mutex_destroy(&mutex_lock);
#endif
#ifdef WIN32
DeleteCriticalSection(&mutex_lock);
#endif