advacing lnux program 互斥信号量[copy]

之前的例子中,我们让几个线程从一个队列中取出并处理任务,每个线程函数都会尝试
从队列中取得任务并当没有任务的时候结束线程函数。如果事先给队列中添加好任务,或者
至少以比处理线程提取任务更快的速度向队列中添加新任务,这个模型没有问题。但如果工
作线程速度太快了,任务列表会被清空而处理线程会退出,而再有新任务到达的时候就没有
线程处理任务了。因此,我们更希望有这样一种机制:让工作线程阻塞以等待新的任务的到
达。
信号量可以很方便地做到这一点。信号量是一个用于协调多个线程的计数器。如互斥体
一样,GNU/Linux保证对信号量的取值和赋值操作都是安全的,不会造成竞争状态。
每个信号量都有一个非负整数作为计数。信号量支持两种基本操作:

· “等待”(wait)操作会将信号量的值减一。如果信号量的值已经是一,这个操作
会阻塞直到(由于其它线程的一些操作)信号量的值成为正值。当信号量的值成为
正值的时候,等待操作会返回,同时信号量的值减一。
· “投递”(post)操作会将信号量的值加一。如果信号量之前的值为零,并且有其
它线程在等待过程中阻塞,其中一个线程就会解除阻塞状态并结束等待状态(同时
将信号量的值重置为0)。

需要注意的是GNU/Linux提供了两种有少许不同的信号量实现。一种是我们这里所说
的兼容POSIX标准的信号量实现。当处理线程之间的通信的时候可以使用这种实现。另一
种实现常用于进程间通信,在5.2 节“进程信号量”中进行了介绍。如果要使用信号量,
应包含头文件<semaphore.h>。

信号量是用sem_t类型的变量表示的。在使用一个信号量之前,你需要通过sem_init函
数对它进行初始化;sem_init接受一个指向这个信号量变量的指针作为第一个参数。第二个
参数应为0
2,而第三个参数则指定了信号量的初始值。当你不再需要一个信号量之后,应
该调用sem_destory销毁它。
我们可以用sem_wait对一个信号量执行等待操作,用sem_post对一个信号量执行投递
操作。同时GNU/Linux还提供了一个非阻塞版本的信号量等待函数sem_trywait。这个函数
类似pthread_mutex_trylock——如果当时的情况应该导致阻塞,这个函数会立即返回错误
代码EAGAIN而不是造成线程阻塞。
GNU/Linux同时提供了一个用于获取信号量当前值的函数sem_getvalue。这个函数将信
号量的值保存在第二个参数(指向一个int类型变量的指针)所指向的变量中。不过,你不
应使用从这个函数得到的值作为判断应该执行等待还是投递操作的依据。因为这样做可能导
致竞争状态的出现:其它线程可能在sem_getvalue 和随后的其它信号量函数之间开始执行
并修改信号量的值。应使用属于原子操作的等待和投递代替这种做法。
回到我们的任务队列例子中。我们可以使用一个信号量来计算在队列中等待处理的任务
数量。代码列表4.12使用一个信号量控制队列。函数enqueue_job负责向队列中添加一个
任务。

代码列表4.12 (job-queue3.c) 用信号量控制的任务队列
#include <malloc.h>
#include <pthread.h>
#include <semaphore.h>
struct job {
/* 维护链表结构用的成员。*/
struct job* next;
/* 其它成员,用于描述任务。*/
};
/* 等待执行的任务队列。*/
struct job* job_queue;

/* 用于保护job_queue 的互斥体。*/
pthread_mutex_t job_queue_mutex = PTHREAD_MUTEX_INITIALIZER;
/* 用于计数队列中任务数量的信号量。*/
sem_t job_queue_count;
/* 对任务队列进行唯一的一次初始化。*/
void initialize_job_queue ()
{
/* 队列在初始状态为空。*/
job_queue = NULL;
/* 初始化用于计数队列中任务数量的信号量。它的初始值应为0。*/
sem_init (&job_queue_count, 0, 0);
}
/* 处理队列中的任务,直到队列为空。*/
void* thread_function (void* arg)
{
while (1) {
struct job* next_job;
/* 等待任务队列信号量。如果值为正,则说明队列中有任务,应将信号量值减一。
如果队列为空,阻塞等待直到新的任务加入队列。*/
sem_wait (&job_queue_count);
/* 锁定队列上的互斥体。*/
pthread_mutex_lock (&job_queue_mutex);
/* 因为检测了信号量,我们确信队列不是空的。获取下一个任务。*/
next_job = job_queue;
/* 将这个任务从队列中移除。*/
job_queue = job_queue->next;
/* 解除队列互斥体的锁定因为我们已经不再需要操作队列。*/
pthread_mutex_unlock (&job_queue_mutex);
/* 处理任务。*/
process_job (next_job);
/* 清理资源。*/
free (next_job);
}
return NULL;
}

/* 向任务队列添加新的任务。*/
void enqueue_job (/* 在这里传递特定于任务的数据……*/)
{
struct job* new_job;
/* 分配一个新任务对象。*/
new_job = (struct job*) malloc (sizeof (struct job));
/* 在这里设置任务中的其它字段……*/
/* 在访问任务队列之前锁定列表。*/
pthread-mutex_lock (&job_queue_mutex);
/* 将新任务加入队列的开端。*/
new_job->next = job_queue;
job_queue = new_job;
/* 投递到信号量通知有新任务到达。如果有线程被阻塞等待信号量,一个线程就会恢复执行并处
理这个任务。*/
sem_post (&job_queue_count);
/* 将任务队列解锁。*/
pthread_mutex_unlock (&job_queue_mutex);
}
在从队列前端取走任务之前,每个线程都会等待信号量。如果信号量的值是0,则说明
任务队列为空,线程会阻塞,直到信号量的值恢复正值(表示有新任务到达)为止。
函数enqueue_job将一个任务添加到队列中。就如同thread_function函数,它需要在
修改队列之前锁定它。在将任务添加到队列之后,它将信号量的值加一以表示有新任务到达。
在列表4.12 中的版本中,工作线程永远不会退出;当没有任务的时候所有线程都会在
sem_wait中阻塞。

原文地址:https://www.cnblogs.com/michile/p/2891131.html