【操作系统】同步工具 锁,信号量,条件变量 - hippowc/hippowc.github.io GitHub Wiki
- 互斥锁是用在多线程多任务互斥的,一个线程占用了某一个资源,那么别的线程就无法访问,直到这个线程unlock,其他的线程才开始可以利用这个资源。
- 信号量用在多线程多任务同步的,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动作
- 条件变量用在多线程环境下实现更复杂的条件控制。
信号量不一定是锁定某一个资源,而是流程上的概念,比如:有A,B两个线程,B线程要等A线程完成某一任务以后再进行自己下面的步骤,这个任务并不一定是锁定某一资源,还可以是进行一些计算或者数据处理之类。而线程互斥量则是“锁住某一资源”的概念,在锁定期间内,其他线程无法对被保护的数据进行操作。在有些情况下两者可以互换。
futex(快速用户区互斥的简称)是一个在Linux上实现锁定和构建高级抽象锁如信号量和POSIX互斥的基本工具。它们第一次出现在内核开发的2.5.7版;其语义在2.5.40固定下来,然后在2.6.x系列稳定版内核中出现。
Futex 是fast userspace mutex的缩写,意思是快速用户空间互斥体。Linux内核把它们作为快速的用户空间的锁和信号量的预制构件提供给开发者。Futex 由一块能够被多个进程共享的内存空间(一个对齐后的整型变量)组成;这个整型变量的值能够通过汇编语言调用CPU提供的原子操作指令来增加或减少,并且一个进程可以等待直到那个值变成正数。Futex 的操作几乎全部在应用程序空间完成;只有当操作结果不一致从而需要仲裁时,才需要进入操作系统内核空间执行。这种机制允许使用 futex 的锁定原语有非常高的执行效率:由于绝大多数的操作并不需要在多个进程之间进行仲裁,所以绝大多数操作都可以在应用程序空间执行,而不需要使用(相对高代价的)内核系统调用。
锁的使用场景:
锁保护的是资源的访问,所以加锁的对象应该是对受保护资源进行操作的代码。
/* 定义一个锁,用于保护 job_queue 的互斥体。*/
pthread_mutex_t job_queue_mutex = PTHREAD_MUTEX_INITIALIZER;
/* 锁定队列上的互斥体。*/
pthread_mutex_lock (&job_queue_mutex);
// 进行代码处理
/* 解除队列互斥体的锁定。*/
pthread_mutex_unlock (&job_queue_mutex);
信号量的使用场景:
信号量(semaphore)是用于进程中传递信号的一个整数值。
- 一个信号量可以初始化为非负值
- semWait操作可以使信号量减1,若信号量的值为负,则执行semWait的进程被阻塞。否则进程继续执行。
- semSignal操作使信号量加1。
基本函数
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);//创建信号量
int sem_post(sem_t *sem);// +1操作
int sem_wait(sem_t *sem);// -1操作
int sem_destroy(sem_t *sem); //销毁
使用伪代码
semaphore s=1;
void p(int i)
{
while(true)
{
semWait(s);
//临界区
semSignal(s);
//其他部分
}
一个简单的生产消费的例子:
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
sem_t *s_count; // 信号量
void produce(void)
{
while(1){
sleep(1);
sem_post(s_count); // 信号量增加1
}
}
void consume(void)
{
while(1){
sleep(1);
sem_wait(s_count); // 信号量减少1,信号量等于0时等待
}
}
int main(int argc, char const *argv[])
{
s_count = sem_open("name_sem", O_CREAT|O_RDWR, 0666, 1);
// 一个生产者两个消费者
pthread_t p;
pthread_t c1;
pthread_create(&p, NULL, &produce, NULL);
pthread_create(&c1, NULL, &consume, NULL);
pthread_join(p, NULL);
pthread_join(c1, NULL);
return 0;
}
条件变量的使用场景:
设你要写一个永久循环的线程,每次循环的时候执行一些任务。不过这个线程循环需 要被一个标志控制:只有当标志被设置的时候才运行,标志被清除的时候线程暂停。
你可以通过在不断自旋(重复循环)以实现这一点。每次循环的 时候,线程都检查这个标志是否被设置。因为有多个线程都要访问这个标志,我们使用一个 互斥体保护它。这种实现虽然可能是正确的,但是效率不尽人意。当标志没有被设置的时候, 线程会不断循环检测这个标志,同时会不断锁定、解锁互斥体,浪费 CPU 时间。你真正需 要的是这样一种方法:当标志没有设置的时候让线程进入休眠状态;而当某种特定条件出现时,标志位被设置,线程被唤醒。
条件变量将允许你实现这样的目的:在一种情况下令线程继续运行,而相反情况下令线程阻塞。只要每个可能涉及到改变状态的线程正确使用条件变量,Linux 将保证当条件改变 的时候由于一个条件变量的状态被阻塞的线程均能够被激活。
如同信号量,线程可以对一个条件变量执行等待操作。如果线程 A 正在等待一个条件 变量,它会被阻塞直到另外一个线程,设为线程 B,向同一个条件变量发送信号以改变其 状态。
如果 B 在 A 执行等待操作之前发送了信号,这个信号就丢失了,同时 A 会一直阻塞直到其它线程再次发送信号到这个条件变量。
GNU Linux中提供的同步工具:互斥锁,信号量,条件变量