您的位置:首页 > 其它

线程同步机制之信号量

2017-08-17 15:50 211 查看
一、什么是信号量

线程的信号量与进程间通信中使用的信号量的概念是一样,它是一种特殊的变量,它可以被增加或减少,但对其的关键访问被保证是原子操作。如果一个程序中有多个线程试图改变一个信号量的值,系统将保证所有的操作都将依次进行。
而只有0和1两种取值的信号量叫做二进制信号量,在这里将重点介绍。而信号量一般常用于保护一段代码,使其每次只被一个执行线程运行。我们可以使用二进制信号量来完成这个工作

(原子操作:一组不会被打打断的操作,一旦开始,就运行到结束)

二、信号量的操作

信号量的函数都以sem_开头,线程中使用的基本信号量函数有4个,它们都声明在头文件semaphore.h中。

(1)创建信号量

#include <semaphore.h>

int sem_init(sem_t *sem, int pshared, unsigned int value);

 功能:初始化一个未命名的信号量(unnamed semaphore)。
 sem指向需要初始化的信号量(sem_t类型)。
 value指定信号量的初始值。
 pshared表明信号量是在一个进程的多个线程之间共享还是在多个进程之间共享。若pshared为0,信号量被一个进程的多个线程共享,此时应该将信号量(sem_t)置于所有线程可见的位置(全局变量或动态分配)。
 执行成功返回0,出错返回-1,并设置errno。
 
注意:初始化一个已经初始化了的信号量将导致未定义的行为。

(2)信号量的控制

#include <semaphore.h>

 

int sem_post(sem_t *sem);   // v

int sem_wait(sem_t *sem);   // p
 
sem_post的作用是以原子操作的方式给信号量的值加1
sem_wait函数以原子操作的方式将信号量的值减1,但它会等待直到信号量有个非零值才会开始减法操作。例如,对值为2的信号量调用sem_wait,线程将继续执行,但信号量的值会减到1。如果对值为0的信号量调用sem_wait,这个函数就会等待,直到有其它线程增加了该信号量的值使其不再为0为止。
    如果两个线程同时在sem_wait函数上等待同一个信号量变为非零值,那么当该信号量被第三个线程增加1时,只有其中一个等待线程将开始对信号量减1,然后继续执行,另外一个线程还将继续等待。

 补充:还有另外一个信号量函数sem_trywait,它是sem_wait的非阻塞版本。

(3)信号量的销毁

#include <semaphore.h>

int sem_destroy(sem_t *sem);
这个函数的作用是,用完信号量后对它进行清理,清理该信号量所拥有的资源。如果你试图清理的信号量正被一些线程等待,就会收到一个错误。

(4)应用:生产者与消费者模型

多线程并发应用程序有一个经典的模型,即生产者/消费者模型。系统中,产生消息的是生产者,处理消息的是消费者,消费者和生产者通过一个缓冲区进行消息传递。生产者产生消息后提交到缓冲区,然后通知消费者可以从中取出消息进行处理。消费者处理完信息后,通知生产者可以继续提供消息。

        要实现这个模型,关键在于消费者和生产者这两个线程进行同步。也就是说:只有缓冲区中有消息时,消费者才能够提取消息;只有消息已被处理,生产者才能产生消息提交到缓冲区。

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <time.h>
#include <string.h>

// 信号量和缓冲区
struct data
{
sem_t empty; // 用来控制生产者,只有缓冲区为空,生产者才可以生产消息
sem_t full; // 用来控制消费者,只有缓冲区有数据,才可以消费
char buf[32]; // 消息缓冲区
};

struct data msg;

// 生产者线程工作函数
void *Produce(void *v)
{
char *buf[] = {"苹果", "梨", "香蕉", "榴莲", "橙子", "西瓜", "芒果", "火龙果"};

while (1)
{
// 只有当缓冲区空才能进,生产消息
sem_wait(&msg.empty);

strcpy(msg.buf, buf[rand()%8]);
printf ("放了一个水果: %s\n", msg.buf);

int time = rand() % 100 + 1;
usleep(time*10000);

// 生产完了,通知消费者进行消费
sem_post(&msg.full);
}
}

// 消费者线程工作函数
void *Consum(void *v)
{
char buf[32];
while (1)
{
// 只有当缓冲区不为空才能进,消费消息
sem_wait(&msg.full);

strcpy(buf, msg.buf);
printf ("吃了一个 %s\n", buf);

int time = rand() % 100 + 1;
usleep(time*10000);

// 消费完了,通知生产则会进行生产
sem_post(&msg.empty);
}
}

int main()
{
srand ((unsigned int)time(NULL));

// 初始化信号量
sem_init(&msg.empty, 0, 1); // 生产者,一开始要生产消息
sem_init(&msg.full, 0, 0); // 消费者,一开始要不能消费消息

pthread_t produceId;
pthread_t consumId;
// 创建生产者线程
pthread_create(&produceId, NULL, Produce, NULL);

// 创建消费者线程
pthread_create(&consumId, NULL, Consum, NULL);

// 等待线程结束
pthread_join(produceId, NULL);
pthread_join(consumId, NULL);

// 销毁信号量
sem_destroy(&msg.empty);
sem_destroy(&msg.full);
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: