advacing lnux program –互斥体[copy]

对于刚才这个任务队列竞争状态问题的解决方法就是限制在同一时间只允许一个线程
访问任务队列。当一个线程开始检查任务队列的时候,其它线程应该等待直到第一个线程决
定是否处理任务,并在确定要处理任务时删除了相应任务之后才能访问任务队列。
要实现等待这个操作需要操作系统的支持。GNU/Linux提供了互斥体(mutex,全称
UTual EXclusion locks,互斥锁)。互斥体是一种特殊的锁:同一时刻只有一个线程可以锁
定它。当一个锁被某个线程锁定的时候,如果有另外一个线程尝试锁定这个互斥体,则这第
二个线程会被阻塞,或者说被置于等待状态。只有当第一个线程释放了对互斥体的锁定,第
二个线程才能从阻塞状态恢复运行。GNU/Linux保证当多个线程同时锁定一个互斥体的时
候不会产生竞争状态;只有一个线程可能成功锁定,其它线程均将被阻塞。
将互斥体想象成一个盥洗室的门锁。第一个到达门口的人进入盥洗室并且锁上门。如果
盥洗室被占用期间有第二个人想要使用,他将发现门被锁住因此自己不得不在门外等待,直
到里面的人离开。
要创建一个互斥体,首先需要创建一个pthread_mutex_t类型的变量,并将一个指向这
个变量的指针作为参数调用pthread_mutex_init。而pthread_mutex_init的第二个参数是一
个指向互斥体属性对象的指针;这个对象决定了新创建的互斥体的属性。与pthread_create
一样,如果属性对象指针为NULL,则默认属性将被赋予新建的互斥体对象。这个互斥体变
量只应被初始化一次。下面这段代码展示了创建和初始化互斥体的方法。
pthread_mutex_t mutex;
pthread_mutex_init (&mutex, NULL);
另外一个相对简单的方法是用特殊值PTHREAD_MUTEX_INITIALIZER对互斥体变
量进行初始化。这样就不必再调用pthread_mutex_init进行初始化。这对于全局变量(及C++
中的静态成员变量)的初始化非常有用。因此上面那段代码也可以写成这样:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
线程可以通过调用pthread_mutex_lock尝试锁定一个互斥体。如果这个互斥体没有被
锁定,则这个函数调用会锁定它然后立即返回。如果这个互斥体已经被另一个线程锁定,则
thread_mutex_lock会阻塞调用线程的运行,直到持有锁的线程解除了锁定。同一时间可以
有多个线程在一个互斥体上阻塞。当这个互斥体被解锁,只有一个线程(以不可预知的方式
被选定的)会恢复执行并锁定互斥体,其它线程仍将处于锁定状态。
调用pthread_mutex_unlock将解除对一个互斥体的锁定。始终应该从锁定了互斥体的
线程调用这个函数进行解锁。
代码列表4.11展示了另外一个版本的任务队列。现在我们用一个互斥体保护了这个队
列。访问这个队列之前(不论读写)每个线程都会锁定一个互斥体。只有当检查队
任务的整个过程完成,锁定才会被解除。这样可以防止前面提到的竞争状态的出
代码列表4.11 (job-queue2.c) 任务队列线程函数,用互斥体保护
#include <malloc.h>
#include <pthread.h>
struct job {
/* 维护链表结构用的成员。*/
struct job* next;
/* 其它成员,用于描述任务。*/
};
/* 等待执行的任务队列。*/
struct job* job_queue;
/* 保护任务队列的互斥体。*/
pthread_mutex_t job_queue_mutex = PTHREAD_MUTEX_INITIALIZER;
/* 处理队列中剩余的任务,直到所有任务都经过处理。*/
void* thread_function (void* arg)
{
while (1) {
struct job* next_job;
/* 锁定保护任务队列的互斥体。*/
pthread_mutex_lock (&job_queue_mutex);
/* 现在可以安全地检查队列中是否为空。*/
if (job_queue == NULL)
next_job = NULL;
else {
/* 获取下一个任务。*/
next_job = job_queue;
/* 从任务队列中删除刚刚获取的任务。*/
job_queue = job_queue->next;
}
/* 我们已经完成了对任务队列的处理,因此解除对保护队列的互斥体的锁定。
pthread_mutex_nlock (&job_queue_mutex);
/* 任务队列是否已经为空?如果是,结束线程。*/
if (next_job == NULL)
break;

/* 执行任务。*/
proces_job (next_job);
/* 释放资源。*/
free (next_job);
}
return NULL; 
 所有对job_queue 这个共享的指针的访问都在pthread_mutex_lock 和
pthread_mutex_unlock两个函数调用之间进行。任何一个next_job指向的任务对象,都是
在从队列中移除之后才处理的;这个时候其它线程都无法继续访问这个对象。
注意当队列为空(也就是job_queue为空)的时候我们没有立刻跳出循环,因为如果立
刻跳出,互斥对象将继续保持锁定状态从而导致其它线程再也无法访问整个任务队列。实际
上,我们通过设定next_job为空来标识这个状态,然后在将互斥对象解锁之后再跳出循环。
用互斥对象锁定job_queue不是自动完成的;你必须自己选择是否在访问job_queue之
前锁定互斥体对象以防止并发访问。如下例,向任务队列中添加一个任务的函数可以写成这
个样子:
void enqueue_job (struct job* new_job)
{
pthread_mutex_lock (&job_queue_mutex);
new_job->next = job_queue;
job_queue = new-job;
pthread_mutex_unlock (&job_queue_mutex);
}

互斥体提供了一种由一个线程阻止另一个线程执行的机制。这个机制导致了另外一类软
件错误的产生:死锁。当一个或多个线程处于等待一个不可能出现的情况的状态的时候,我
们称之为死锁状态。
最简单的死锁可能出现在一个线程尝试锁定一个互斥体两次的时候。当这种情况出现的
时候,程序的行为取决于所使用的互斥体的种类。共有三种互斥体:
· 锁定一个快速互斥体(fast mutex,默认创建的种类)会导致死锁的出现。任何对
锁定互斥体的尝试都会被阻塞直到该互斥体被解锁的时候为止。但是因为锁定该互
斥体的线程在同一个互斥体上被锁定,它永远无法接触互斥体上的锁定。
· 锁定一个递归互斥体(recursive mutex)不会导致死锁。递归互斥体可以很安全地
被锁定多次。递归互斥体会记住持有锁的线程调用了多少次pthread_mutex_lock;
持有锁的线程必须调用同样次数的pthread_mutex_unlock以彻底释放这个互斥体
上的锁而使其它线程可以锁定该互斥体。
· 当尝试第二次锁定一个纠错互斥体(error-checking mutex)的时候,GNU/Linux会
自动检测并标识对纠错互斥体上的双重锁定;这种双重锁定通常会导致死锁的出
现。第二次尝试锁定互斥体时pthread_mutex_lock会返回错误码EDEADLK。

高级Linux程序设计·卷一·Linux平台上的高级UNIX编程  完美废人 译
默认情况下GNU/Linux系统中创建的互斥体是第一种,快速互斥体。要创建另外两种
互斥体,首先应声明一个pthread_mutexattr_t类型的变量并且以它的地址作为参数调用
pthread_mutexattr_init函数,以对它进行初始化。然后调用pthread_mutexattr_setkind_np
函数设置互斥体的类型;该函数的第一个参数是指向互斥体属性对象的指针,第二个参数如
果是PTHREAD_MUTEX_RECURSIVE_NP 则创建一个递归互斥体,或者如果是
PTHREAD_MUTEX_ERRORCHECK_NP 则创建的将是一个纠错互斥体。当调 用
pthread_mutex_init的时候传递一个指向这个属性对象的指针以创建一个对应类型的互斥
体,之后调用pthread_mutexattr_destroy销毁属性对象。
下面的代码片断展示了如何创建一个纠错互斥体;
pthread_mutexattr_t attr;
pthread_mutex_t mutex;
pthread_mutexattr_init (&attr);
pthread_mutexattr_setkind_np (&attr, PTHREAD_MUTEX_ERRORCHECK_NP);
pthread_mutex_init (&mutex, &attr);
pthread_mutexattr_destroy (&attr);
如“np”后缀所指明的,递归和纠错两种互斥体都是GNU/Linux 独有的,不具有可移
植性(译者注:np 为non-portable 缩写)。因此,通常不建议在程序中使用这些类型的互
斥体。(当然,纠错互斥体对查找程序中的错误可能很有帮助。)

4.4.4 非阻塞互斥体测试
有时候我们需要检测一个互斥体的状态却不希望被阻塞。例如,一个线程可能需要锁定
一个互斥体,但当互斥体已经锁定的时候,这个线程还可以处理其它的任务。因为
pthread_mutex_lock会阻塞直到互斥体解锁为止,所以我们需要其它的一些函数来达到我们
的目的。
GNU/Linux提供了pthread_mutex_trylock函数作此用途。当你对一个解锁状态的互斥
体调用pthread_mutex_trylock时,就如调用pthread_mutex_lock一样会锁定这个互斥体;
pthread_mutex_trylock 会返回0。而当互斥体已经被其它线程锁定的时候,
pthread_mutex_trylock不会阻塞。相应的,pthread_mutex_trylock会返回错误码EBUSY。
持有锁的其它线程不会受到影响。你可以稍后再次尝试锁定这个互斥体。

原文链接: https://www.cnblogs.com/michile/archive/2013/02/03/2891118.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

    advacing lnux program --互斥体[copy]

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/77565

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月9日 下午6:01
下一篇 2023年2月9日 下午6:02

相关推荐