pthread_mutex_lock()的属性介绍

本文深入探讨了互斥锁在多线程编程中的作用,包括其基本概念、使用方法以及如何避免常见的死锁问题。通过一个简单的读/写程序示例,展示了如何使用互斥锁来确保数据的一致性和线程安全性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

pthread_mutex_lock

pthread_mutex_lock(pthread_mutex_t *mutex);

int pthread_mutex_trylock(pthread_mutex_t *mutex);

int pthread_mutex_unlock(pthread_mutex_t *mutex);

     描述 pthread_mutex_lock()函数锁住由mutex指定的mutex 对象。如果mutex已经被锁住,调用这个函数的线程阻塞直到mutex可用为止。这跟函数返回的时候参数mutex指定的mutex对象变成锁住状态, 同时该函数的调用线程成为该mutex对象的拥有者。

     如果mutex 对象的type是 PTHREAD_MUTEX_NORMAL,不进行deadlock detection(死锁检测)。企图进行relock 这个mutex会导致deadlock. 如果一个线程对未加锁的或已经unlock的mutex对象进行unlock操作,结果是不未知的。

     如果mutex类型是 PTHREAD_MUTEX_ERRORCHECK,那么将进行错误检查。如果一个线程企图对一个已经锁住的mutex进行relock,将返回一个错 误。如果一个线程对未加锁的或已经unlock的mutex对象进行unlock操作,将返回一个错误。

     如果mutex类型是 PTHREAD_MUTEX_RECURSIVE,mutex会有一个锁住次数(lock count)的概念。当一个线程成功地第一次锁住一个mutex的时候,锁住次数(lock count)被设置为1,每一次一个线程unlock这个mutex的时候,锁住次数(lock count)就减1。当锁住次数(lock count)减少为0的时候,其他线程就能获得该mutex锁了。如果一个线程对未加锁的或已经unlock的mutex对象进行unlock操作,将返 回一个错误。

    如果mutex类型是 PTHREAD_MUTEX_DEFAULT,企图递归的获取这个mutex的锁的结果是不确定的。unlock一个不是被调用线程锁住的mutex的结 果也是不确定的。企图unlock一个未被锁住的mutex导致不确定的结果。

    pthread_mutex_trylock()调用在参数mutex指定的mutex对象当前被锁住的时候立即返回,除此之外,pthread_mutex_trylock()跟pthread_mutex_lock()功能完全一样。

    pthread_mutex_unlock()函数释放有参数mutex指定的mutex对象的锁。如果被释放取决于该Mutex对象的类型属性。如果有多个线程为了获得该mutex锁阻塞,调用pthread_mutex_unlock()将是该mutex可用,一定的调度策略将被用来决定哪个线程可以获得该mutex锁。(在mutex类型为PTHREAD_MUTEX_RECURSIVE 的情况下,只有当lock count 减为0并且调用线程在该mutex上已经没有锁的时候)(翻译到这里,才觉得我的这个锁概念是多么模糊) 如果一个线程在等待一个mutex锁得时候收到了一个signal,那么在从signal handler返回的时候,该线程继续等待该mutex锁,就像这个线程没有被中断一样。

    返回值成功

    pthread_mutex_lock() 和 pthread_mutex_unlock() 返回0,否则返回一个错误的提示码

    pthread_mutex_trylock() 在成功获得了一个mutex的锁后返回0,否则返回一个错误提示码错误

    pthread_mutex_lock() 和 pthread_mutex_unlock()失败的时候 [EINVAL] mutex在生成的时候,它的protocol属性的值是 PTHREAD_PRIO_PROTECT,同时调用线程的优先级(priority)比该mutex的当前prority上限高

pthread_mutex_trylock() 函数在一下情况会失败:

[EBUSY] The mutex could not be acquired because it was already locked. mutex已经被锁住的时候无法再获取锁

The pthread_mutex_lock(), pthread_mutex_trylock() and pthread_mutex_unlock() functions may fail if:

[EINVAL] mutex指向的mutex未被初始化

[EAGAIN] Mutex的lock count(锁数量)已经超过 递归索的最大值,无法再获得该mutex锁

pthread_mutex_lock() 函数在一下情况下会失败:

[EDEADLK] 当前线程已经获得该mutex锁

pthread_mutex_unlock() 函数在以下情况下会失败:

 [EPERM] 当前线程不是该mutex锁的拥有者 所有的这些函数的错误返回值都不会是[EINTR]

互斥锁

互斥锁用来保证一段时间内只有一个线程在执行一段代码。必要性显而易见:假设各个线程向同一个文件顺序写入数据,最后得到的结果一定是灾难性的。

我们先看下面一段代码。这是一个读/写程序,它们公用一个缓冲区,并且我们假定一个缓冲区只能保存一条信息。即缓冲区只有两个状态:有信息或没有信息。

void reader_function ( void );
void writer_function ( void );

char buffer;
int buffer_has_item=0;
pthread_mutex_t mutex;
struct timespec delay;
void main ( void ){
pthread_t reader;
/* 定义延迟时间*/
delay.tv_sec = 2;
delay.tv_nec = 0;
/* 用默认属性初始化一个互斥锁对象*/
pthread_mutex_init (&mutex,NULL);
pthread_create(&reader, pthread_attr_default, (void *)&reader_function), NULL);
writer_function( );
}

void writer_function (void){
while(1){
/* 锁定互斥锁*/
pthread_mutex_lock (&mutex);
if (buffer_has_item==0){
buffer=make_new_item( );
buffer_has_item=1;
}
/* 打开互斥锁*/
pthread_mutex_unlock(&mutex);
pthread_delay_np(&delay);
}
}

void reader_function(void){
while(1){
pthread_mutex_lock(&mutex);
if(buffer_has_item==1){
consume_item(buffer);
buffer_has_item=0;
}
pthread_mutex_unlock(&mutex);
pthread_delay_np(&delay);
}
}


这里声明了互斥锁变量mutex,结构pthread_mutex_t为不公开的数据类型,其中包含一个系统分配的属性对象。函数 pthread_mutex_init用来生成一个互斥锁。NULL参数表明使用默认属性。如果需要声明特定属性的互斥锁,须调用函数 pthread_mutexattr_init。函数pthread_mutexattr_setpshared和函数 pthread_mutexattr_settype用来设置互斥锁属性。前一个函数设置属性pshared,它有两个取 值,PTHREAD_PROCESS_PRIVATE和PTHREAD_PROCESS_SHARED。前者用来不同进程中的线程同步,后者用于同步本进 程的不同线程。在上面的例子中,我们使用的是默认属性PTHREAD_PROCESS_ PRIVATE。后者用来设置互斥锁类型,可选的类型有PTHREAD_MUTEX_NORMAL、PTHREAD_MUTEX_ERRORCHECK、 PTHREAD_MUTEX_RECURSIVE和PTHREAD _MUTEX_DEFAULT。它们分别定义了不同的上所、解锁机制,一般情况下,选用最后一个默认属性。

pthread_mutex_lock声明开始用互斥锁上锁,此后的代码直至调用pthread_mutex_unlock为止,均被上锁,即同一时间只 能被一个线程调用执行。当一个线程执行到pthread_mutex_lock处时,如果该锁此时被另一个线程使用,那此线程被阻塞,即程序将等待到另一 个线程释放此互斥锁。在上面的例子中,我们使用了pthread_delay_np函数,让线程睡眠一段时间,就是为了防止一个线程始终占据此函数。

上面的例子非常简单,就不再介绍了,需要提出的是在使用互斥锁的过程中很有可能会出现死锁:两个线程试图同时占用两个资源,并按不同的次序锁定相应的互 斥锁,例如两个线程都需要锁定互斥锁1和互斥锁2,a线程先锁定互斥锁1,b线程先锁定互斥锁2,这时就出现了死锁。此时我们可以使用函数 pthread_mutex_trylock,它是函数pthread_mutex_lock的非阻塞版本,当它发现死锁不可避免时,它会返回相应的信 息,程序员可以针对死锁做出相应的处理。另外不同的互斥锁类型对死锁的处理不一样,但最主要的还是要程序员自己在程序设计注意这一点。
<think>我们正在讨论pthread_mutex_lock的实现原理和使用示例。根据提供的引用,我们可以总结出一些关键点。 首先,引用[1]提到pthread_mutex_lock是多线程编程中的一个重要函数,用于锁定互斥锁,确保同一时间只有一个线程可以访问临界区。引用[2]给出了一个简单的使用示例,展示了如何声明和初始化一个互斥锁,以及如何使用pthread_mutex_lockpthread_mutex_unlock。引用[3]则通过一个实际的例子说明了在没有锁的情况下,多个线程竞争会导致计数错误,而使用锁后可以保证计数的正确性。 关于实现原理,由于pthread库是系统相关的,其具体实现可能因操作系统而异。在Linux系统中,pthread_mutex_lock通常是通过系统调用(如futex)来实现的。futex(快速用户空间互斥锁)是一种在用户空间和内核空间之间协作的锁机制,它通过在用户空间检查一个变量来避免不必要的系统调用,从而在无竞争的情况下提高效率。当发生竞争时,会通过系统调用让内核来挂起等待的线程。 使用示例: 1. 声明和初始化互斥锁:可以使用静态初始化(PTHREAD_MUTEX_INITIALIZER)或动态初始化(pthread_mutex_init)。 2. 在进入临界区之前调用pthread_mutex_lock加锁。 3. 在离开临界区之后调用pthread_mutex_unlock解锁。 下面我们给出一个简单的代码示例,展示如何使用pthread_mutex_lock来保护一个共享变量。 ```c #include <pthread.h> #include <stdio.h> // 静态初始化互斥锁 static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; static int shared_counter = 0; void *thread_func(void *arg) { for (int i = 0; i < 10000; i++) { // 加锁 pthread_mutex_lock(&mutex); shared_counter++; // 解锁 pthread_mutex_unlock(&mutex); } return NULL; } int main() { pthread_t t1, t2; pthread_create(&t1, NULL, thread_func, NULL); pthread_create(&t2, NULL, thread_func, NULL); pthread_join(t1, NULL); pthread_join(t2, NULL); printf("shared_counter = %d\n", shared_counter); // 应该输出20000 return 0; } ``` 在这个例子中,两个线程分别对共享变量shared_counter增加10000次,由于使用了互斥锁,最终结果将是20000。如果没有互斥锁,结果可能会小于20000,因为两个线程可能会同时修改shared_counter导致部分更新丢失。 关于实现原理的更多细节,由于涉及操作系统内核,我们不会在这里深入展开,但可以简要说明: - 当线程调用pthread_mutex_lock时,它会尝试通过原子操作获取锁。如果锁当前没有被占用,那么线程将获得锁并继续执行。 - 如果锁已经被其他线程占用,那么当前线程会被阻塞,直到锁被释放。在底层,这通常通过futex系统调用来实现,将线程放入等待队列,并在锁释放时唤醒。 注意:使用互斥锁时需要注意避免死锁,例如确保加锁顺序一致,以及及时释放锁。 §§相关问题§§ 1. pthread_mutex_lockpthread_mutex_trylock 有什么区别? 2. 在多线程编程中,除了互斥锁,还有哪些同步机制? 3. 如何避免使用互斥锁时出现死锁?</think>### pthread_mutex_lock 实现原理及使用示例 #### 一、实现原理 `pthread_mutex_lock` 是 POSIX 线程库中的互斥锁实现,其核心原理基于原子操作和系统调用: 1. **用户空间优化** 通过 **futex(快速用户空间互斥锁)** 机制实现[^1]: - 无竞争时:完全在用户空间通过原子操作(如 `cmpxchg`)获取锁,无需系统调用 - 有竞争时:通过 `futex(FUTEX_WAIT)` 系统调用让线程休眠,等待唤醒 2. **锁状态管理** 使用 32 位整型表示锁状态: - `0`:未锁定 - `1`:锁定(无等待线程) - `2`:锁定(有等待线程) 3. **加锁流程** ```c while (true) { if (原子操作设置锁状态为"锁定") 成功: 返回成功; else: futex(FUTEX_WAIT); // 线程休眠 } ``` 4. **解锁流程** ```c 原子操作设置锁状态为"未锁定"; if (有等待线程): futex(FUTEX_WAKE); // 唤醒一个等待线程 ``` #### 二、使用示例 ```c #include <pthread.h> #include <stdio.h> // 声明共享资源和互斥锁 static int counter = 0; static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 静态初始化[^2] void* thread_func(void* arg) { for (int i = 0; i < 10000; i++) { // 加锁保护临界区 pthread_mutex_lock(&mutex); counter++; pthread_mutex_unlock(&mutex); } return NULL; } int main() { pthread_t t1, t2; // 创建两个线程 pthread_create(&t1, NULL, thread_func, NULL); pthread_create(&t2, NULL, thread_func, NULL); // 等待线程结束 pthread_join(t1, NULL); pthread_join(t2, NULL); printf("Final counter: %d\n", counter); // 正确输出 20000 return 0; } ``` #### 三、关键注意事项 1. **锁顺序** 多个锁使用时需固定加锁顺序,避免死锁[^1] 2. **临界区精简** 保持临界区代码尽可能短,例如: ```c // 正确示例(临界区仅包含共享操作) pthread_mutex_lock(&mutex); shared_var++; pthread_mutex_unlock(&mutex); // 错误示例(包含非共享操作) pthread_mutex_lock(&mutex); printf("Debug: %d", shared_var); // I/O操作会阻塞其他线程 shared_var++; pthread_mutex_unlock(&mutex); ``` 3. **错误处理** 检查返回值: ```c if (pthread_mutex_lock(&mutex) != 0) { perror("Lock failed"); // 错误处理 } ``` 4. **锁的初始化** - 静态初始化:`pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;` - 动态初始化:`pthread_mutex_init(&mutex, NULL);` #### 四、性能对比 引用[^3]中的测试结果: ``` 无锁时计数: 2935 // 因竞争导致数据丢失 有锁时计数: 3000 // 结果正确 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值