互斥锁Mutex和信号量

 

Mutex是一把钥匙,一个人拿了就可进入一个房间,出来的时候把钥匙交给队列的第一个。一般的用法是用于串行化对critical section代码的访问,保证这段代码不会被并行的运行。

 

Is a key to a toilet. One person can have the key - occupy the toilet - at the time. When finished, the person gives (frees) the key to the next person in the queue.

Officially: "Mutexes are typically used to serialise access to a section of re-entrant code that cannot be executed concurrently by more than one thread. A mutex object only allows one thread into a controlled section, forcing other threads which attempt to gain access to that section to wait until the first thread has exited from that section."

Semaphore是一件可以容纳N人的房间,如果人不满就可以进去,如果人满了,就要等待有人出来。对于N=1的情况,称为binary semaphore。一般的用法是,用于限制对于某一资源的同时访问。

 

Semaphore:

Is the number of free identical toilet keys. Example, say we have four toilets with identical locks and keys. The semaphore count - the count of keys - is set to 4 at beginning (all four toilets are free), then the count value is decremented as people are coming in. If all toilets are full, ie. there are no free keys left, the semaphore count is 0. Now, when eq. one person leaves the toilet, semaphore is increased to 1 (one free key), and given to the next person in the queue.

Officially: "A semaphore restricts the number of simultaneous users of a shared resource up to a maximum number. Threads can request access to the resource (decrementing the semaphore), and can signal that they have finished using the resource (incrementing the semaphore)." Ref: Symbian Developer Library

信号量限制一份资源的最大同步个数。线程可以请求进入资源,并在用完资源时发出信号.

对于Binary semaphore与Mutex,这两者之间就存在了很多相似之处:
在有的系统中Binary semaphore与Mutex是没有差异的。在有的系统上,主要的差异是mutex一定要由获得锁的进程来释放。而semaphore可以由其它进程释放(这时的semaphore实际就是个原子的变量,大家可以加或减),因此semaphore可以用于进程间同步。Semaphore的同步功能是所有系统都支持的,而Mutex能否由其他进程释放则未定,因此建议mutex只用于保护critical section。而semaphore则用于保护某变量,或者同步。

1、Mutex 互斥量/互斥锁

Mutex本质上说就是一把锁,提供对资源的独占访问,所以Mutex的主要作用是用于互斥的访问共享资源

Mutex对象的值,只有0和1两个值。这两个值也分别代表了Mutex的两种状态。

值为0,表示锁定状态,当前对象被锁定,用户进程/线程如果试图Lock临界资源,则进入排队等待。

值为1,表示空闲状态,当前对象为空闲,用户进程/线程可以Lock临界资源,之后Mutex值减1变为0.

Mutex可以被抽象为四个操作:创建 Create;加锁 Lock;解锁 Unlock;销毁 Destroy。

Mutex被创建时可以有初始值,表示Mutex被创建后,是锁定状态还是空闲状态。

在同一个线程中,为了防止死锁,系统不允许连续两次对Mutex加锁。就是加锁和解锁这两个对应的操作,需要在同一个线程中完成

不同操作系统中提供的Mutex函数:

 

动作/系统WindowsLinuxSolaris
创建CreateMutexpthread_mutex_initmutex_init
加锁WaitForSingleObjectpthread_mutex_lockmutex_lock
解锁ReleaseMutexpthread_mutex_unlockmutex_unlock
销毁CloseHandlepthread_mutex_destroymutex_destroy

避免互斥锁死锁的几个基本原则:

-- 对共享资源操作前一定要获得锁。

-- 完成操作以后一定要释放锁。

-- 尽量短时间的占用锁,即占用锁的时间不要过长。

-- 如果有多锁,如获得顺序是ABC连环扣,释放顺序也应该是ABC。

-- 线程错误返回时应该释放它所获得的锁。

>>“挂起等待”和“唤醒等待线程”的操作?

每个Mutex有一个等待队列,一个线程要在Mutex上挂起等待,首先把自己加入等待队列中,然后置线程状态为睡眠,然后调用调度器函数切换到别的线程。一个线程要唤醒等待队列中的其它线程,只需从等待队列中取出一项,把它的状态从睡眠改为就绪,加入就绪队列,那么下次调度器函数执行时就有可能切换到被唤醒的线程。

一般情况下,如果同一个线程先后两次调用lock,在第二次调用时,由于锁已经被占用,该线程会挂起等待别的线程释放锁,然而锁正是被自己占用着的,该线程又被挂起而没有机会释放锁,因此就永远处于挂起等待状态了,这叫做死锁(Deadlock)。

另一种典型的死锁情形是这样:线程A获得了锁1,线程B获得了锁2,这时线程A调用lock试图获得锁2,结果是需要挂起等待线程B释放锁2,而这时线程B也调用lock试图获得锁1,结果是需要挂起等待线程A释放锁1,于是线程A和线程B都永远处于挂起状态了。如果涉及更多的线程和更多的锁,则死锁的可能性更大。(例子,上厕所,要锁厕所门)

2、信号量 semaphore

信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施,它负责协调各个线程,以保证它们能够正确、合理的使用公共资源。信号量可以分为几类:

-- 二进制信号量(binary semaphore):只允许信号量取0或1值,其同时只能被一个线程获取。

-- 整型信号量(integer semaphore):信号量取值是整数,它可以被多个线程同时获得,直到信号量的值变为0.

-- 记录型信号量(record semaphore):每个信号量s除一个整数值value(计数)外,还有一个等待队列List,其中是阻塞在该信号量的各个线程的标识。当信号量被释放一个,值被加1后,系统自动从等待队列中唤醒一个等待中的线程,让其获得信号量,同时信号量再减1.

信号量通过一个计数器控制对共享资源的访问,信号量的值是一个非负整数,所有通过它的线程都会将该整数减1.如果计数器大于0,则访问被允许;如果为0,则访问被禁止,所有试图通过它的线程都将处于等待状态。

计数器计算的结果是允许访问共享资源的通行证。因此,为了访问共享资源,线程必须从信号量得到通行证,如果该信号量的计数大于0,则此线程获得一个通行证,这将导致信号量的计数递减,否则,此线程将阻塞直到获得一个通行证为止。

当此线程不再需要访问共享资源时,它释放该通行证,这导致信号量的计数递增,如果另一个线程等待通行证,则那个线程将在那时获得通行证。

Semaphore可以被抽象为5个操作:

>> 创建 Create

>> 等待 Wait,线程等待信号量,如果值>0,则获得,值减1;如果<=0,则线程进入睡眠状态,直到信号量值>0或超时。

>> 释放 Post,执行释放信号量,则值加+1,如果此时有正在等待的线程,则唤醒该线程。

>> 试图等待 TryWait,线程并不真正的去获得信号量,而是检查信号量是否能够被获得,如果信号量值大于0,则TryWait返回成功,否则返回失败。

>> 销毁 Destroy

信号量,是可以用来保护两个或多个关键代码段,这些关键代码段不能并发调用。在进入一个关键代码段之前,线程必须获取一个信号量。首先要创建一个信号量,然后将Acquire Semaphore 以及Release Semaphore分别放置在每个关键代码段的首末段。

POSIX Semaphore(Named Semaphores / Unnamed Semaphores)

Named Semaphores:this type of semaphore has a name. By calling sem_open() with the same name, unrelated process can access the same semaphore.

To work with a named semaphore, we employ the following functions:

-- The sem_open() function opens or create a semaphore, initializes the semaphore if it is created by the call, and returns a handle for use in later calls.

-- The sem_post(sem) and sem_wait(sem) functions respectively increment and decrement a semaphore's value.

-- The sem_getvalue() function retrieves a semaphore's current value.

-- The sem_close() function removes the calling process's association with a semaphore that it previously opened.

-- The sem_unlink() function removes a semaphore name and marks the semaphore for deletion when all processes have closed it.

3)互斥量和信号量的区别:

>> 互斥量用于线程的互斥,信号量用于线程的同步。

这是互斥量和信号量的根本区别,也就是互斥和同步之间的区别。

互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排他性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。

同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况下允许多个访问者同时访问资源。

>> 互斥量值只能是0/1,信号量值可以为非负整数。

也就是说,一个互斥量只能用于一个资源的互斥访问,它不能实现多个资源的多线程互斥问题。信号量可以实现多个同类资源的多线程互斥和同步。当信号量为单值信号量时,也可以完成一个资源的互斥访问。

>> 互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到。

  • 10
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,这是一个关于生产者消费者问题的描述。以下是一个简单的实现,包含主程序、生产者进程和消费者进程: ```c #include <stdio.h> #include <pthread.h> #include <semaphore.h> #define BUFFER_SIZE 10 char buffer[BUFFER_SIZE]; int buffer_index; pthread_mutex_t mutex; sem_t full, empty; void *producer(void *arg) { while (1) { // 生成数据项 char item = 'A' + rand() % 26; // 若缓冲区满,等待信号量empty sem_wait(&empty); // 获取互斥锁mutex pthread_mutex_lock(&mutex); // 将数据项放入缓冲区 buffer[buffer_index++] = item; // 释放互斥锁mutex pthread_mutex_unlock(&mutex); // 发送信号量full sem_post(&full); } } void *consumer(void *arg) { while (1) { // 若缓冲区空,等待信号量full sem_wait(&full); // 获取互斥锁mutex pthread_mutex_lock(&mutex); // 取出缓冲区中的数据项 char item = buffer[--buffer_index]; // 释放互斥锁mutex pthread_mutex_unlock(&mutex); // 发送信号量empty sem_post(&empty); // 处理数据项 printf("Consumed item: %c\n", item); } } int main() { buffer_index = 0; // 初始化互斥量和信号量 pthread_mutex_init(&mutex, NULL); sem_init(&full, 0, 0); sem_init(&empty, 0, BUFFER_SIZE); // 创建消费者线程和生产者线程,启动线程 pthread_t producer_thread, consumer_thread; pthread_create(&producer_thread, NULL, producer, NULL); pthread_create(&consumer_thread, NULL, consumer, NULL); // 等待子线程完成后,销毁互斥量和信号量 pthread_join(producer_thread, NULL); pthread_join(consumer_thread, NULL); pthread_mutex_destroy(&mutex); sem_destroy(&full); sem_destroy(&empty); return 0; } ``` 这个程序中,我们首先定义了一个缓冲区数组和一个缓冲区索引变量,用于存放生产者生成的数据项。接着,我们定义了一个互斥mutex和两个信号量full、empty,分别表示缓冲区是否满或空。 在生产者进程中,我们使用while循环不断生成数据项。如果缓冲区已满,使用sem_wait()函数等待信号量empty。获取互斥锁mutex后,将数据项放入缓冲区中,然后释放互斥锁mutex。最后,使用sem_post()函数发送信号量full,通知消费者进程可以从缓冲区中取出数据项了。 在消费者进程中,我们也使用while循环不断消费数据项。如果缓冲区为空,使用sem_wait()函数等待信号量full。获取互斥锁mutex后,从缓冲区中取出数据项,并将缓冲区索引减一。然后,释放互斥锁mutex,并使用sem_post()函数发送信号量empty,通知生产者进程可以向缓冲区中放入数据项了。最后,我们处理取出的数据项,并输出到控制台上。 在主程序中,我们首先初始化互斥量和信号量。然后,创建消费者线程和生产者线程,并启动线程。最后,使用pthread_join()函数等待子线程完成后,销毁互斥量和信号量。 希望我的回答能够解决你的问题!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值