互斥锁详解

使用互斥锁

表 4–3 列出了用来处理互斥锁的函数。

表 4–3 互斥锁的例程

操作

相关函数说明

初始化互斥锁

pthread_mutex_init 语法

使互斥锁保持一致

pthread_mutex_consistent_np 语法

锁定互斥锁

pthread_mutex_lock 语法

解除锁定互斥锁

pthread_mutex_unlock 语法

使用非阻塞互斥锁锁定

pthread_mutex_trylock 语法

销毁互斥锁

pthread_mutex_destroy 语法

缺省调度策略 SCHED_OTHER 不指定线程可以获取锁的顺序。如果多个线程正在等待一个互斥锁,则获取顺序是不确定的。出现争用时,缺省行为是按优先级顺序解除线程的阻塞。

初始化互斥锁

使用 pthread_mutex_init(3C) 可以使用缺省值初始化由 mp 所指向的互斥锁,还可以指定已经使用pthread_mutexattr_init() 设置的互斥锁属性。mattr 的缺省值为 NULL。对于 Solaris 线程,请参见mutex_init(3C) 语法

pthread_mutex_init 语法

 
int pthread_mutex_init(pthread_mutex_t *mp,    const pthread_mutexattr_t *mattr);
#include <pthread.h>
pthread_mutex_t mp = PTHREAD_MUTEX_INITIALIZER;
pthread_mutexattr_t mattr;
int ret;/* initialize a mutex to its default value */
ret = pthread_mutex_init(&mp, NULL);
/* initialize a mutex */
ret = pthread_mutex_init(&mp, &mattr);
 

如果互斥锁已初始化,则它会处于未锁定状态。互斥锁可以位于进程之间共享的内存中或者某个进程的专用内存中。


注 – 初始化互斥锁之前,必须将其所在的内存清零。

将 mattr 设置为 NULL 的效果与传递缺省互斥锁属性对象的地址相同,但是没有内存开销。

使用 PTHREAD_MUTEX_INITIALIZER 宏可以将以静态方式定义的互斥锁初始化为其缺省属性。

当其他线程正在使用某个互斥锁时,请勿重新初始化或销毁该互斥锁。如果任一操作没有正确完成,将会导致程序失败。如果要重新初始化或销毁某个互斥锁,则应用程序必须确保当前未使用该互斥锁。

pthread_mutex_init 返回值

pthread_mutex_init() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。

EBUSY

描述:

该实现已检测到系统尝试重新初始化 mp 所引用的对象,即以前进行过初始化但 尚未销毁的互斥锁。

EINVAL

描述:

mattr 属性值无效。互斥锁尚未修改。

EFAULT

描述:

mp 所指向的互斥锁的地址无效。

使互斥保持一致

如果某个互斥锁的属主失败,该互斥锁可能会变为不一致。

使用 pthread_mutex_consistent_np 可使互斥对象 mutex 在其属主停止之后保持一致。

pthread_mutex_consistent_np 语法

#include <pthread.h>
int pthread_mutex_consistent_np(pthread_mutex_t *mutex);

注 –

仅当定义了 _POSIX_THREAD_PRIO_INHERIT 符号时,pthread_mutex_consistent_np() 才适用,并且仅适用于使用协议属性值 PTHREAD_PRIO_INHERIT 初始化的互斥锁。


调用 pthread_mutex_lock() 会获取不一致的互斥锁。EOWNWERDEAD 返回值表示出现不一致的互斥锁。

持有以前通过调用 pthread_mutex_lock() 获取的互斥锁时可调用pthread_mutex_consistent_np()

如果互斥锁的属主失败,则该互斥锁保护的临界段可能会处于不一致状态。在这种情况下,仅当互斥锁保护的临界段可保持一致时,才能使该互斥锁保持一致。

针对互斥锁调用 pthread_mutex_lock()pthread_mutex_unlock() 和pthread_mutex_trylock() 会以正常方式进行。

对于一致或者未持有的互斥锁,pthread_mutex_consistent_np() 的行为是不确定的。

pthread_mutex_consistent_np 返回值

pthread_mutex_consistent_np() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。

pthread_mutex_consistent_np() 会在出现以下情况时失败:

ENOSYS

描述:

选项 _POSIX_THREAD_PRIO_INHERIT 未定义,或者该实现不支持pthread_mutex_consistent_np()

pthread_mutex_consistent_np() 可能会在出现以下情况时失败:

EINVAL

描述:

mattr 属性值无效。

锁定互斥锁

使用 pthread_mutex_lock(3C) 可以锁定 mutex 所指向的互斥锁。

pthread_mutex_lock 语法

 
int pthread_mutex_lock(pthread_mutex_t *mutex);
#include <pthread.h>
pthread_mutex_t mutex;int ret;
ret = pthread_ mutex_lock(&mp); /* acquire the mutex */
 

当 pthread_mutex_lock() 返回时,该互斥锁已被锁定。调用线程是该互斥锁的属主。如果该互斥锁已被另一个线程锁定和拥有,则调用线程将阻塞,直到该互斥锁变为可用为止。 对于 Solaris 线程,请参见mutex_lock 语法

如果互斥锁类型为 PTHREAD_MUTEX_NORMAL,则不提供死锁检测。尝试重新锁定互斥锁会导致死锁。如果某个线程尝试解除锁定的互斥锁不是由该线程锁定或未锁定,则将产生不确定的行为。

如果互斥锁类型为 PTHREAD_MUTEX_ERRORCHECK,则会提供错误检查。如果某个线程尝试重新锁定的互斥锁已经由该线程锁定,则将返回错误。如果某个线程尝试解除锁定的互斥锁不是由该线程锁定或者未锁定,则将返回错误。

如果互斥锁类型为 PTHREAD_MUTEX_RECURSIVE,则该互斥锁会保留锁定计数这一概念。线程首次成功获取互斥锁时,锁定计数会设置为 1。线程每重新锁定该互斥锁一次,锁定计数就增加 1。线程每解除锁定该互斥锁一次,锁定计数就减小 1。 锁定计数达到 0 时,该互斥锁即可供其他线程获取。如果某个线程尝试解除锁定的互斥锁不是由该线程锁定或者未锁定,则将返回错误。

如果互斥锁类型是 PTHREAD_MUTEX_DEFAULT,则尝试以递归方式锁定该互斥锁将产生不确定的行为。对于不是由调用线程锁定的互斥锁,如果尝试解除对它的锁定,则会产生不确定的行为。如果尝试解除锁定尚未锁定的互斥锁,则会产生不确定的行为。

pthread_mutex_lock 返回值

pthread_mutex_lock() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。

EAGAIN

描述:

由于已超出了互斥锁递归锁定的最大次数,因此无法获取该互斥锁。

EDEADLK

描述:

当前线程已经拥有互斥锁。

如果定义了 _POSIX_THREAD_PRIO_INHERIT 符号,则会使用协议属性值 PTHREAD_PRIO_INHERIT 对互斥锁进行初始化。此外,如果 pthread_mutexattr_setrobust_np() 的 robustness 参数是PTHREAD_MUTEX_ROBUST_NP,则该函数将失败并返回以下值之一:

EOWNERDEAD

描述:

该互斥锁的最后一个属主在持有该互斥锁时失败。该互斥锁现在由调用方拥有。调用方必须尝试使该互斥锁所保护的状态一致。

如果调用方能够使状态保持一致,请针对该互斥锁调用 pthread_mutex_consistent_np() 并解除锁定该互斥锁。以后对 pthread_mutex_lock() 的调用都将正常进行。

如果调用方无法使状态保持一致,请勿针对该互斥锁调用 pthread_mutex_init(),但要解除锁定该互斥锁。以后调用 pthread_mutex_lock() 时将无法获取该互斥锁,并且将返回错误代码ENOTRECOVERABLE

如果获取该锁的属主失败并返回 EOWNERDEAD,则下一个属主获取该锁时将返回 EOWNERDEAD

ENOTRECOVERABLE

描述:

尝试获取的互斥锁正在保护某个状态,此状态由于该互斥锁以前的属主在持有该锁时失败而导致不可恢复。尚未获取该互斥锁。如果满足以下条件,则可能出现此不可恢复的情况:

  • 以前获取该锁时返回 EOWNERDEAD

  • 该属主无法清除此状态

  • 该属主已经解除锁定了该互斥锁,但是没有使互斥锁状态保持一致

ENOMEM

描述:

已经超出了可同时持有的互斥锁数目的限制。

解除锁定互斥锁

使用 pthread_mutex_unlock(3C) 可以解除锁定 mutex 所指向的互斥锁。 对于 Solaris 线程,请参见mutex_unlock 语法

pthread_mutex_unlock 语法

 
int pthread_mutex_unlock(pthread_mutex_t *mutex);
#include <pthread.h>
pthread_mutex_t mutex;int ret;
ret = pthread_mutex_unlock(&mutex); /* release the mutex */
 

pthread_mutex_unlock() 可释放 mutex 引用的互斥锁对象。互斥锁的释放方式取决于互斥锁的类型属性。 如果调用 pthread_mutex_unlock() 时有多个线程被 mutex 对象阻塞,则互斥锁变为可用时调度策略可确定获取该互斥锁的线程。 对于 PTHREAD_MUTEX_RECURSIVE 类型的互斥锁,当计数达到零并且调用线程不再对该互斥锁进行任何锁定时,该互斥锁将变为可用。

pthread_mutex_unlock 返回值

pthread_mutex_unlock() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下情况,该函数将失败并返回对应的值。

EPERM

描述:

当前线程不拥有互斥锁。

使用非阻塞互斥锁锁定

使用 pthread_mutex_trylock(3C) 可以尝试锁定 mutex 所指向的互斥锁。对于 Solaris 线程,请参见mutex_trylock 语法

pthread_mutex_trylock 语法

 
int pthread_mutex_trylock(pthread_mutex_t *mutex);
#include <pthread.h>
pthread_mutex_t mutex;int ret;
ret = pthread_mutex_trylock(&mutex); /* try to lock the mutex */
 

pthread_mutex_trylock() 是 pthread_mutex_lock() 的非阻塞版本。如果 mutex 所引用的互斥对象当前被任何线程(包括当前线程)锁定,则将立即返回该调用。否则,该互斥锁将处于锁定状态,调用线程是其属主。

pthread_mutex_trylock 返回值

pthread_mutex_trylock() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。

EBUSY

描述:

由于 mutex 所指向的互斥锁已锁定,因此无法获取该互斥锁。

EAGAIN

描述:

由于已超出了 mutex 的递归锁定最大次数,因此无法获取该互斥锁。

如果定义了 _POSIX_THREAD_PRIO_INHERIT 符号,则会使用协议属性值 PTHREAD_PRIO_INHERIT 对互斥锁进行初始化。此外,如果 pthread_mutexattr_setrobust_np() 的 robustness 参数是PTHREAD_MUTEX_ROBUST_NP,则该函数将失败并返回以下值之一:

EOWNERDEAD

描述:

该互斥锁的最后一个属主在持有该互斥锁时失败。该互斥锁现在由调用方拥有。调用方必须尝试使该互斥锁所保护的状态一致。

如果调用方能够使状态保持一致,请针对该互斥锁调用 pthread_mutex_consistent_np() 并解除锁定该互斥锁。以后对 pthread_mutex_lock() 的调用都将正常进行。

如果调用方无法使状态保持一致,请勿针对该互斥锁调用 pthread_mutex_init(),而要解除锁定该互斥锁。以后调用 pthread_mutex_trylock() 时将无法获取该互斥锁,并且将返回错误代码 ENOTRECOVERABLE

如果已获取该锁的属主失败并返回 EOWNERDEAD,则下一个属主获取该锁时返回 EOWNERDEAD

ENOTRECOVERABLE

描述:

尝试获取的互斥锁正在保护某个状态,此状态由于该互斥锁以前的属主在持有该锁时失败而导致不可恢复。尚未获取该互斥锁。 以下条件下可能会出现此情况:

  • 以前获取该锁时返回 EOWNERDEAD

  • 该属主无法清除此状态

  • 该属主已经解除锁定了该互斥锁,但是没有使互斥锁状态保持一致

ENOMEM

描述:

已经超出了可同时持有的互斥锁数目的限制。

销毁互斥锁

使用 pthread_mutex_destroy(3C) 可以销毁与 mp 所指向的互斥锁相关联的任何状态。 对于 Solaris 线程,请参见mutex_destroy 语法

pthread_mutex_destroy 语法

 
int pthread_mutex_destroy(pthread_mutex_t *mp);
#include <pthread.h>
pthread_mutex_t mp;int ret;
ret = pthread_mutex_destroy(&mp); /* mutex is destroyed */
 

请注意,没有释放用来存储互斥锁的空间。

pthread_mutex_destroy 返回值

pthread_mutex_destroy() 在成功完成之后会返回零。其他任何返回值都表示出现了错误。如果出现以下任一情况,该函数将失败并返回对应的值。

EINVAL

描述:

mp 指定的值不会引用已初始化的互斥锁对象。

互斥锁定的代码示例

示例 4–1 显示了使用互斥锁定的一些代码段。


示例 4–1 互斥锁示例
 
#include <pthread.h>
pthread_mutex_t count_mutex;
long long count;
voidincrement_count()
{     
	pthread_mutex_lock(&count_mutex);    
	count = count + 1;     
	pthread_mutex_unlock(&count_mutex);
}
long longget_count()
{    
	long long c;        
	pthread_mutex_lock(&count_mutex);     
	c = count;    
	pthread_mutex_unlock(&count_mutex);     
	return (c);
}
 

示例 4–1 中的两个函数将互斥锁用于不同目的。increment_count() 函数使用互斥锁确保对共享变量进行原子更新。get_count() 函数使用互斥锁保证以原子方式读取 64 位数量 count。在 32 位体系结构上,long long 实际上是两个 32 位数量。

读取整数值时执行的是原子运算,因为整数是大多数计算机中常见的字长。

锁分层结构的使用示例

有时,可能需要同时访问两个资源。您可能正在使用其中的一个资源,随后发现还需要另一个资源。如果两个线程尝试声明这两个资源,但是以不同的顺序锁定与这些资源相关联的互斥锁,则会出现问题。例如,如果两个线程分别锁定互斥锁 1 和互斥锁 2,则每个线程尝试锁定另一个互斥锁时,将会出现死锁。示例 4–2 说明了可能的死锁情况。


示例 4–2 死锁

线程 1

线程 2

pthread_mutex_lock(&m1);

/* use resource 1 */

pthread_mutex_lock(&m2);

/* use resources 1 and 2 */

pthread_mutex_unlock(&m2);

pthread_mutex_unlock(&m1);

pthread_mutex_lock(&m2);

/* use resource 2 */

pthread_mutex_lock(&m1);

/* use resources 1 and 2 */

pthread_mutex_unlock(&m1);

pthread_mutex_unlock(&m2);


避免此问题的最佳方法是,确保线程在锁定多个互斥锁时,以同样的顺序进行锁定。如果始终按照规定的顺序锁定,就不会出现死锁。此方法称为锁分层结构,它通过为互斥锁指定逻辑编号来对这些锁进行排序。

另外,请注意以下限制:如果您持有的任何互斥锁其指定编号大于 n,则不能提取指定编号为 n 的互斥锁。

但是,不能始终使用此方法。有时,必须按照与规定不同的顺序提取互斥锁。要防止在这种情况下出现死锁,请使用 pthread_mutex_trylock()。如果线程发现无法避免死锁时,该线程必须释放其互斥锁。


示例 4–3 条件锁定

线程 1

线程 2

pthread_mutex_lock(&m1); pthread_mutex_lock(&m2);

/* no processing */

pthread_mutex_unlock(&m2);

pthread_mutex_unlock(&m1);

for (; ;)

{ pthread_mutex_lock(&m2);

if(pthread_mutex_trylock(&m1)==0)

/* got it */

break;

/* didn't get it */

pthread_mutex_unlock(&m2);

}

/* get locks; no processing */

pthread_mutex_unlock(&m1);

pthread_mutex_unlock(&m2);


示例 4–3 中,线程 1 按照规定的顺序锁定互斥锁,但是线程 2 不按顺序提取互斥锁。要确保不会出现死锁,线程 2 必须非常小心地提取互斥锁 1。如果线程 2 在等待该互斥锁释放时被阻塞,则线程 2 可能刚才已经与线程 1 进入了死锁状态。

要确保线程 2 不会进入死锁状态,线程 2 需要调用 pthread_mutex_trylock(),此函数可在该互斥锁可用时提取它。如果该互斥锁不可用,线程 2 将立即返回并报告提取失败。此时,线程 2 必须释放互斥锁 2。线程 1 现在会锁定互斥锁 2,然后释放互斥锁 1 和互斥锁 2。

嵌套锁定和单链接列表的结合使用示例

示例 4–4 和示例 4–5 说明了如何同时提取三个锁。通过按照规定的顺序提取锁可避免出现死锁。


示例 4–4 单链接列表结构
 
typedef struct node1 {    
	int value;    
	struct node1 *link;    
	pthr ead_mutex_t lock;
} node1_t;
node1_t ListHead;

本示例针对每个包含一个互斥锁的节点使用单链接列表结构。要将某个节点从列表中删除,请首先从 ListHead 开始搜索列表,直到找到所需的节点为止。ListHead 永远不会被删除。

要防止执行此搜索时产生并发删除,请在访问每个节点的任何内容之前先锁定该节点。由于所有的搜索都从 ListHead 开始,并且始终按照列表中的顺序提取锁,因此不会出现死锁。

因为更改涉及到两个节点,所以找到所需的节点之后,请锁定该节点及其前序节点。因为前序节点的锁总是最先提取,所以可再次防止出现死锁。示例 4–5 说明如何使用 C 代码来删除单链接列表中的项。


示例 4–5 单链接列表和嵌套锁定
 
node1_t *delete(int value)
{    
	node1_t *prev, *current;    
	prev = &ListHead;    
	pthread_mutex_lock(&prev->lock);    
	while ((current = prev->link) != NULL) 
	{        
		pthread_mutex_lock(¤t->lock);        
		if (current->value == value) 
		{            
			prev->link = current->link;            
			pthread_mutex_unlock(¤t->lock);            
			pthread_mutex_unlock(&prev->lock);            
			current->link = NULL;            
			return(current);        
		}        
		pthread_mutex_unlock(&prev->lock);        
		prev = current;    
	}    
	pthread_mutex_unlock(&prev->lock);    
	return(NULL);
}
 

嵌套锁定和循环链接列表的示例

示例 4–6 通过将以前的列表结构转换为循环列表来对其进行修改。由于不再存在用于标识的头节点,因该线程可以与特定的节点相关联,并可针对该节点及其邻居执行操作。锁分层结构在此处不适用,因为链接之后的分层结构明显是循环结构。


示例 4–6 循环链接列表结构
 
typedef struct node2 {    
	int value;    
	struct node2 *link;    
	pthread_mutex_t lock;
} node2_t;
 

以下的 C 代码用来获取两个节点上的锁并执行涉及到这两个锁的操作。


示例 4–7 循环链接列表和嵌套锁定
 
void Hit Neighbor(node2_t *me) 
{   
	while (1) 
	{        
		pthread_mutex_lock(&me->lock);        
		if (pthread_mutex_lock(&me->link->lock)!= 0)
		{            /* failed to get lock */                                                  				pthread_mutex_unlock(&me->lock); 
				continue;                 
		}                 	
		break;         
	}         
	me->link->value += me->value;         
	me->value /=2;         
	pthread_mutex_unlock(&me->link->lock);         
	pthread_mutex_unlock(&me->lock);
}


                
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
互斥的使用通常包括以下几个步骤: 1. 创建互斥对象。 2. 在访问共享资源之前,获取互斥。 3. 访问共享资源。 4. 在访问完共享资源后,释放互斥。 下面是使用C语言的pthread库实现互斥的操作步骤及相关函数及参数: 1. 创建互斥对象: ```C #include <pthread.h> pthread_mutex_t mutex; int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr); ``` 参数说明: - mutex:互斥对象指针。 - attr:互斥属性,可以为NULL。 该函数用于创建互斥对象,并初始化互斥属性。 2. 获取互斥: ```C #include <pthread.h> int pthread_mutex_lock(pthread_mutex_t *mutex); ``` 参数说明: - mutex:互斥对象指针。 该函数用于获取互斥,如果当前互斥已经被其他线程持有,则该线程会被阻塞,直到获得互斥为止。 3. 访问共享资源: 这一步是具体业务逻辑的处理,不需要使用特定的函数。 4. 释放互斥: ```C #include <pthread.h> int pthread_mutex_unlock(pthread_mutex_t *mutex); ``` 参数说明: - mutex:互斥对象指针。 该函数用于释放互斥,以便其他线程可以获得互斥并访问共享资源。 5. 销毁互斥对象: ```C #include <pthread.h> int pthread_mutex_destroy(pthread_mutex_t *mutex); ``` 参数说明: - mutex:互斥对象指针。 该函数用于销毁互斥对象,释放相关资源。注意,调用该函数之前必须确保所有的线程都已经释放了互斥

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值