线程同步--互斥锁,读写锁

线程同步

基本概念

线程的能力在于能够方便地通过全局变量或共享内存来交换信息,但这也带来了并发控制的复杂性,主要表现在如何安全地管理多个线程对共享资源的访问。这里涉及到几个关键的概念和技术:

临界区(Critical Section)

临界区是指那些访问共享资源(如全局变量、文件等)的代码段。为保证程序的正确性和避免数据竞争,这些代码段的执行必须是原子的,即在执行完整个代码段之前,不应被其他线程打断管理临界区的方法主要包括使用互斥锁(mutexes)、信号量(semaphores)、读写锁(read-write locks)等同步机制

线程同步

线程同步是指使线程以一种安全和确定的方式访问共享资源的各种机制。主要同步技术包括:

  1. 互斥锁(Mutex)

    • 用于保证同时只有一个线程可以进入临界区。线程在进入临界区之前必须获取锁,退出临界区时释放锁。如果锁已被其他线程占用,线程将阻塞直到锁被释放。
  2. 信号量(Semaphores)

    • 用于控制对共享资源的访问。信号量有一个计数器,表示可用资源的数量。线程在访问资源前需通过调用wait()操作来减少计数器,若计数器为零,则线程阻塞直到资源变为可用。完成资源访问后,线程调用signal()操作增加计数器。
  3. 条件变量(Condition Variables)

    • 允许线程在某些条件下阻塞,并在这些条件改变时被唤醒。通常与互斥锁配合使用,用于线程之间的协调和状态同步。
  4. 读写锁(Read-Write Locks)

    • 允许多个读操作同时进行,但写操作需要独占访问。这种锁是优化读取操作频繁而写入操作较少的场景。

实践中的应用

使用这些同步技术时,必须注意避免死锁、活锁和饥饿等问题。死锁发生时,多个线程相互等待对方持有的锁,从而无法继续执行。活锁是指线程虽然没有阻塞,但仍然无法向前推进,因为不断重试失败的操作。饥饿发生在某些线程无法获得必需的系统资源。

合理地使用线程同步技术可以显著提高程序在多核处理器上的性能,并发确保数据的一致性和完整性。在设计多线程程序时,开发者应当充分考虑这些因素,确保应用程序的稳定性和效率。如果需要具体的示例或进一步的解释,随时欢迎提问!

案例
/*使用多线程实现买票的案例。有3个窗口,一共是100张票。
*/#include <stdio.h>
#include <pthread.h>
#include <unistd.h>// 全局变量,所有的线程都共享这一份资源。
int tickets = 100;void * sellticket(void * arg) {// 卖票while(tickets > 0) {usleep(6000);printf("%ld 正在卖第 %d 张门票\n", pthread_self(), tickets);tickets--;}return NULL;
}int main() {// 创建3个子线程pthread_t tid1, tid2, tid3;pthread_create(&tid1, NULL, sellticket, NULL);pthread_create(&tid2, NULL, sellticket, NULL);pthread_create(&tid3, NULL, sellticket, NULL);// 回收子线程的资源,阻塞pthread_join(tid1, NULL);pthread_join(tid2, NULL);pthread_join(tid3, NULL);// 设置线程分离。// pthread_detach(tid1);// pthread_detach(tid2);// pthread_detach(tid3);pthread_exit(NULL); // 退出主线程return 0;
}
daic@daic:~/Linux/linuxwebserver/part03Multithreading/lesson30$ gcc selltickets.c -lpthread
daic@daic:~/Linux/linuxwebserver/part03Multithreading/lesson30$ ./a.out 
139630428940032 正在卖第 100 张门票
139630420547328 正在卖第 100 张门票
139630437332736 正在卖第 100 张门票
139630420547328 正在卖第 97 张门票
139630428940032 正在卖第 97 张门票
139630437332736 正在卖第 95 张门票
139630420547328 正在卖第 94 张门票
139630428940032 正在卖第 93 张门票
139630437332736 正在卖第 92 张门票
139630420547328 正在卖第 91 张门票
139630437332736 正在卖第 90 张门票
139630428940032 正在卖第 91 张门票
139630420547328 正在卖第 88 张门票
139630437332736 正在卖第 87 张门票
139630428940032 正在卖第 86 张门票
139630420547328 正在卖第 85 张门票
139630437332736 正在卖第 84 张门票
139630428940032 正在卖第 83 张门票
139630420547328 正在卖第 82 张门票
139630428940032 正在卖第 81 张门票
139630437332736 正在卖第 80 张门票
139630420547328 正在卖第 79 张门票
139630428940032 正在卖第 78 张门票
139630437332736 正在卖第 77 张门票
139630420547328 正在卖第 76 张门票
139630428940032 正在卖第 75 张门票
139630437332736 正在卖第 74 张门票
139630420547328 正在卖第 73 张门票
139630428940032 正在卖第 72 张门票
139630437332736 正在卖第 71 张门票
139630420547328 正在卖第 70 张门票
139630428940032 正在卖第 69 张门票
139630437332736 正在卖第 68 张门票
139630420547328 正在卖第 67 张门票
139630428940032 正在卖第 66 张门票
139630437332736 正在卖第 65 张门票
139630420547328 正在卖第 64 张门票
139630437332736 正在卖第 63 张门票
139630428940032 正在卖第 62 张门票
139630420547328 正在卖第 61 张门票
139630437332736 正在卖第 60 张门票
139630428940032 正在卖第 59 张门票
139630420547328 正在卖第 58 张门票
139630428940032 正在卖第 57 张门票
139630437332736 正在卖第 56 张门票
139630420547328 正在卖第 55 张门票
139630428940032 正在卖第 54 张门票
139630437332736 正在卖第 53 张门票
139630420547328 正在卖第 52 张门票
139630428940032 正在卖第 51 张门票
139630437332736 正在卖第 50 张门票
139630420547328 正在卖第 49 张门票
139630428940032 正在卖第 48 张门票
139630437332736 正在卖第 47 张门票
139630420547328 正在卖第 46 张门票
139630428940032 正在卖第 45 张门票
139630437332736 正在卖第 44 张门票
139630420547328 正在卖第 43 张门票
139630428940032 正在卖第 42 张门票
139630437332736 正在卖第 41 张门票
139630420547328 正在卖第 40 张门票
139630428940032 正在卖第 39 张门票
139630437332736 正在卖第 38 张门票
139630420547328 正在卖第 37 张门票
139630428940032 正在卖第 36 张门票
139630437332736 正在卖第 35 张门票
139630420547328 正在卖第 34 张门票
139630428940032 正在卖第 33 张门票
139630437332736 正在卖第 32 张门票
139630420547328 正在卖第 31 张门票
139630428940032 正在卖第 30 张门票
139630437332736 正在卖第 29 张门票
139630420547328 正在卖第 28 张门票
139630428940032 正在卖第 27 张门票
139630437332736 正在卖第 26 张门票
139630428940032 正在卖第 25 张门票
139630420547328 正在卖第 24 张门票
139630437332736 正在卖第 23 张门票
139630420547328 正在卖第 22 张门票
139630428940032 正在卖第 21 张门票
139630437332736 正在卖第 20 张门票
139630420547328 正在卖第 19 张门票
139630428940032 正在卖第 19 张门票
139630437332736 正在卖第 17 张门票
139630420547328 正在卖第 16 张门票
139630428940032 正在卖第 16 张门票
139630437332736 正在卖第 14 张门票
139630420547328 正在卖第 13 张门票
139630428940032 正在卖第 12 张门票
139630437332736 正在卖第 11 张门票
139630420547328 正在卖第 10 张门票
139630428940032 正在卖第 9 张门票
139630437332736 正在卖第 8 张门票
139630420547328 正在卖第 7 张门票
139630428940032 正在卖第 6 张门票
139630437332736 正在卖第 5 张门票
139630420547328 正在卖第 4 张门票
139630428940032 正在卖第 3 张门票
139630437332736 正在卖第 2 张门票
139630420547328 正在卖第 1 张门票
139630428940032 正在卖第 0 张门票
139630437332736 正在卖第 -1 张门票

互斥锁

互斥量

在多线程编程中,互斥量是同步原语的一种,非常关键于防止所谓的“竞态条件”(race conditions),即多个线程同时访问和修改同一共享资源而引起的不可预测结果。

互斥量的工作原理

  • 状态: 互斥量有两种状态,已锁定(locked)和未锁定(unlocked)。
  • 操作: 主要操作包括锁定(lock)和解锁(unlock)。
  • 所有权: 只有锁定了互斥量的线程才能解锁它,这意味着锁的操作是关联线程所有权的。

使用互斥量的基本模式

当线程需要访问受互斥量保护的共享资源时,它会:

  1. 锁定互斥量: 确保如果其他线程已经锁定了互斥量,当前线程将等待(或阻塞)直到互斥量变为未锁定状态。
  2. 访问共享资源: 在安全的环境中进行操作,因为其他线程将无法同时访问这些资源。
  3. 解锁互斥量: 允许其他线程可以锁定互斥量并访问同一资源。

如果多个线程试图执行这一块代码(一个临界区),事实上只有一个线程能够持有该互斥

量(其他线程将遭到阻塞),即同时只有一个线程能够进入这段代码区域,如下图所示:

image-20240511061940228

互斥量相关操作函数

互斥量的类型 pthread_mutex_t
pthread_mutex_init

函数 pthread_mutex_init 用于初始化互斥量,是 POSIX 线程库中的一个重要函数。初始化是在互斥量使用之前必需的步骤,以确保互斥量在首次使用时处于已知的状态。

函数原型
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
参数说明
  • mutex:指向将要被初始化的 pthread_mutex_t 结构的指针。
  • attr:指向互斥量属性的指针。这个属性可以用来设置互斥量的类型(如普通、递归、错误检查等),如果传递 NULL,则使用默认属性。
返回值
  • 0:成功。
  • 错误码:在失败时返回一个错误码,而不是设置 errno
错误码
  • EINVAL:传递了无效的属性。
  • ENOMEM:没有足够的内存来初始化互斥量。
使用示例

下面是一个简单的示例,展示如何初始化一个默认属性的互斥量,并使用它来同步两个线程的操作:

#include <pthread.h>
#include <stdio.h>pthread_mutex_t lock;void* function(void* arg) {pthread_mutex_lock(&lock);printf("Thread %ld is running\n", (long)arg);pthread_mutex_unlock(&lock);return NULL;
}int main() {pthread_t t1, t2;// 初始化互斥量pthread_mutex_init(&lock, NULL);// 创建两个线程pthread_create(&t1, NULL, function, (void*)1);pthread_create(&t2, NULL, function, (void*)2);// 等待线程结束pthread_join(t1, NULL);pthread_join(t2, NULL);// 销毁互斥量pthread_mutex_destroy(&lock);return 0;
}

在这个示例中,我们创建了一个互斥量 lock,并在两个线程中使用它来同步对 printf 函数的调用,确保在同一时刻只有一个线程可以执行 printf

注意事项

在使用完互斥量后,应调用 pthread_mutex_destroy 来释放任何可能由 pthread_mutex_init 分配的资源。

如果互斥量正在被使用(即处于锁定状态),则尝试初始化互斥量可能会导致未定义行为。因此,初始化应在创建任何使用它的线程之前完成。

正确地初始化互斥量对于确保程序的线程安全至关重要。

pthread_mutex_destroy

函数 pthread_mutex_destroy 用于销毁已经初始化的互斥量,释放它可能占用的资源。在多线程编程中,正确地初始化和销毁互斥量是保证资源正确管理的重要一环。

函数原型
#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);
参数说明
  • mutex:指向将要被销毁的 pthread_mutex_t 结构的指针。
返回值
  • 0:成功。
  • 错误码:如果函数调用失败,将返回一个非零的错误码。
错误码
  • EBUSY:尝试销毁一个正在被锁定或正在被其他线程等待的互斥量。在销毁互斥量之前,必须确保没有线程正在使用它。
  • EINVAL:传递给函数的互斥量指针无效。
注意事项
  • 在销毁互斥量之前,确保互斥量没有被锁定。如果互斥量被某个线程持有或其他线程正在等待锁定该互斥量,那么尝试销毁互斥量可能导致 EBUSY 错误。
  • 通常,pthread_mutex_destroy() 应该在所有使用互斥量的线程结束后调用,以确保所有线程均不会再访问该互斥量。
  • 正确的互斥量管理(初始化、使用、销毁)对于避免资源泄露和潜在的死锁至关重要。

确保遵循这些最佳实践可以帮助维持程序的稳定性和可靠性,尤其是在涉及多线程操作的环境中。

pthread_mutex_lock

pthread_mutex_lock 是 POSIX 线程库中的一个函数,用于在多线程程序中锁定互斥量(mutex)。当一个线程调用此函数时,它将尝试获取指定互斥量的所有权。如果互斥量已经被另一个线程锁定,调用线程将被阻塞,直到互斥量变为可用。这是确保对共享资源访问的互斥(排他性访问)的基本机制。

函数原型
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
参数说明
  • mutex:指向互斥量的指针,该互斥量之前应已通过 pthread_mutex_init 初始化。
返回值
  • 0:成功锁定了互斥量。
  • 错误码:如果函数调用失败,将返回一个非零的错误码。
常见的错误码
  • EINVAL:传递给函数的互斥量指针无效。
  • EDEADLK:如果互斥量已经被当前线程锁定,并且互斥量是不允许递归锁定的,尝试重新锁定可能会返回此错误。
注意事项
  • 使用互斥量时,确保在访问任何受保护的共享资源前锁定互斥量,并在完成访问后释放(解锁)互斥量。
  • 避免在持有互斥量时执行长时间操作或可能导致线程阻塞的调用,以减少对其他线程的影响。
  • 要注意死锁的问题,特别是在多个互斥量涉及的情况下。确保以一致的顺序获取互斥量,避免循环等待条件的出现。

通过这种方式,互斥量帮助实现线程之间的同步,保证程序在并发环境中的正确性和效率。

pthread_mutex_trylock

函数 pthread_mutex_trylock 是 POSIX 线程库中的一个用于尝试锁定互斥量的函数,它与 pthread_mutex_lock 相似,但与后者不同的是,pthread_mutex_trylock 在无法立即获取互斥量时不会阻塞调用线程。这使得它成为避免潜在死锁和减少等待时间的有用工具。

函数原型
#include <pthread.h>
int pthread_mutex_trylock(pthread_mutex_t *mutex);
参数说明
  • mutex:指向互斥量的指针,该互斥量应已通过 pthread_mutex_init 初始化。
返回值
  • 0:成功锁定了互斥量。
  • EBUSY:互斥量已被另一个线程锁定,函数立即返回。
  • EINVAL:传递给函数的互斥量指针无效。
使用场景

pthread_mutex_trylock 非常适合于那些不希望线程在无法获取互斥量时长时间阻塞的场景,例如:

  • 在实时计算中,线程需要快速做出决策,不能因等待互斥量而延误。
  • 在尝试获取多个互斥量时,用于避免死锁,特别是当你无法保证所有线程都以相同顺序请求互斥量时。
示例代码

下面是一个简单的示例,展示如何使用 pthread_mutex_trylock 来尝试锁定互斥量,并根据返回值处理不同的情况:

#include <pthread.h>
#include <stdio.h>pthread_mutex_t mutex;void* trylock_thread(void* arg) {int trylock_result = pthread_mutex_trylock(&mutex);if (trylock_result == 0) {printf("Thread %ld: Got the lock\n", (long)arg);// 对共享资源进行操作pthread_mutex_unlock(&mutex);} else if (trylock_result == EBUSY) {printf("Thread %ld: Mutex is already locked by another thread\n", (long)arg);}return NULL;
}int main() {pthread_t t1, t2;// 初始化互斥量pthread_mutex_init(&mutex, NULL);// 创建两个线程pthread_create(&t1, NULL, trylock_thread, (void*)1);pthread_create(&t2, NULL, trylock_thread, (void*)2);// 等待线程完成pthread_join(t1, NULL);pthread_join(t2, NULL);// 销毁互斥量pthread_mutex_destroy(&mutex);return 0;
}
注意事项
  • pthread_mutex_trylock 可以帮助你设计非阻塞的同步策略,但需要谨慎处理返回值,确保程序逻辑的正确性。
  • 使用此函数时,应考虑适当的错误处理和替代逻辑,特别是在获取锁失败时的操作。
  • 和所有互斥量操作一样,保证每次成功的 pthread_mutex_trylock 调用后都要有相应的 pthread_mutex_unlock 调用来释放互斥量。

通过合理使用 pthread_mutex_trylock,可以增加程序的响应速度和灵活性,同时避免因锁等待导致的性能瓶颈。

pthread_mutex_unlock

函数 pthread_mutex_unlock 是 POSIX 线程库中用于解锁互斥量的函数。这个函数通常在一个线程完成对受保护的共享资源的操作后调用,以释放互斥量,允许其他线程可以锁定此互斥量并访问相同的资源。解锁操作是保证多线程程序中资源共享和线程协作的重要环节。

函数原型
#include <pthread.h>
int pthread_mutex_unlock(pthread_mutex_t *mutex);
参数说明
  • mutex:指向需要被解锁的互斥量的指针。
返回值
  • 0:成功解锁互斥量。
  • 错误码:如果函数调用失败,将返回一个非零的错误码。
常见的错误码
  • EINVAL:传递给函数的互斥量指针无效。
  • EPERM:当前线程不是互斥量的所有者,尝试解锁它将返回这个错误。
注意事项
  • 使用 pthread_mutex_unlock 时必须确保当前线程确实是互斥量的所有者,否则会返回错误。
  • 通常情况下,每一个 pthread_mutex_lock 调用都应该对应一个 pthread_mutex_unlock 调用,以避免造成死锁。
  • 在设计使用互斥量的代码时,需要谨慎处理可能导致提前退出函数(如通过 return 语句或异常处理)的逻辑,确保即使在这些情况下互斥量也能被正确解锁。

通过合理使用互斥量的锁定与解锁操作,可以有效管理线程之间对共享资源的访问,保障数据的完整性和程序的稳定运行。

案例
/*互斥量的类型 pthread_mutex_tint pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);- 初始化互斥量- 参数 :- mutex : 需要初始化的互斥量变量- attr : 互斥量相关的属性,NULL- restrict : C语言的修饰符,被修饰的指针,不能由另外的一个指针进行操作。pthread_mutex_t *restrict mutex = xxx;pthread_mutex_t * mutex1 = mutex;int pthread_mutex_destroy(pthread_mutex_t *mutex);- 释放互斥量的资源int pthread_mutex_lock(pthread_mutex_t *mutex);- 加锁,阻塞的,如果有一个线程加锁了,那么其他的线程只能阻塞等待int pthread_mutex_trylock(pthread_mutex_t *mutex);- 尝试加锁,如果加锁失败,不会阻塞,会直接返回。int pthread_mutex_unlock(pthread_mutex_t *mutex);- 解锁
*/
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>// 全局变量,所有的线程都共享这一份资源。
int tickets = 100;// 创建一个互斥量,放在全局区
pthread_mutex_t mutex;void * sellticket(void * arg) {// 卖票while(1) {// 加锁pthread_mutex_lock(&mutex);//临界区if(tickets > 0) {usleep(6000);printf("%ld 正在卖第 %d 张门票\n", pthread_self(), tickets);tickets--;}else {// 解锁pthread_mutex_unlock(&mutex);break;}// 解锁pthread_mutex_unlock(&mutex);}return NULL;
}int main() {// 初始化互斥量pthread_mutex_init(&mutex, NULL);// 创建3个子线程pthread_t tid1, tid2, tid3;pthread_create(&tid1, NULL, sellticket, NULL);pthread_create(&tid2, NULL, sellticket, NULL);pthread_create(&tid3, NULL, sellticket, NULL);// 回收子线程的资源,阻塞pthread_join(tid1, NULL);pthread_join(tid2, NULL);pthread_join(tid3, NULL);pthread_exit(NULL); // 退出主线程// 释放互斥量资源pthread_mutex_destroy(&mutex);return 0;
}
daic@daic:~/Linux/linuxwebserver/part03Multithreading/lesson30$ gcc mutex.c -lpthread
daic@daic:~/Linux/linuxwebserver/part03Multithreading/lesson30$ ./a.out 
140100915386112 正在卖第 100 张门票
140100915386112 正在卖第 99 张门票
140100915386112 正在卖第 98 张门票
140100915386112 正在卖第 97 张门票
140100915386112 正在卖第 96 张门票
140100915386112 正在卖第 95 张门票
140100915386112 正在卖第 94 张门票
140100915386112 正在卖第 93 张门票
140100915386112 正在卖第 92 张门票
140100915386112 正在卖第 91 张门票
140100915386112 正在卖第 90 张门票
140100915386112 正在卖第 89 张门票
140100915386112 正在卖第 88 张门票
140100915386112 正在卖第 87 张门票
140100915386112 正在卖第 86 张门票
140100915386112 正在卖第 85 张门票
140100915386112 正在卖第 84 张门票
140100915386112 正在卖第 83 张门票
140100915386112 正在卖第 82 张门票
140100915386112 正在卖第 81 张门票
140100915386112 正在卖第 80 张门票
140100915386112 正在卖第 79 张门票
140100915386112 正在卖第 78 张门票
140100915386112 正在卖第 77 张门票
140100915386112 正在卖第 76 张门票
140100915386112 正在卖第 75 张门票
140100915386112 正在卖第 74 张门票
140100915386112 正在卖第 73 张门票
140100915386112 正在卖第 72 张门票
140100915386112 正在卖第 71 张门票
140100915386112 正在卖第 70 张门票
140100915386112 正在卖第 69 张门票
140100915386112 正在卖第 68 张门票
140100915386112 正在卖第 67 张门票
140100915386112 正在卖第 66 张门票
140100915386112 正在卖第 65 张门票
140100915386112 正在卖第 64 张门票
140100915386112 正在卖第 63 张门票
140100915386112 正在卖第 62 张门票
140100915386112 正在卖第 61 张门票
140100915386112 正在卖第 60 张门票
140100915386112 正在卖第 59 张门票
140100915386112 正在卖第 58 张门票
140100915386112 正在卖第 57 张门票
140100915386112 正在卖第 56 张门票
140100915386112 正在卖第 55 张门票
140100915386112 正在卖第 54 张门票
140100915386112 正在卖第 53 张门票
140100915386112 正在卖第 52 张门票
140100915386112 正在卖第 51 张门票
140100915386112 正在卖第 50 张门票
140100915386112 正在卖第 49 张门票
140100915386112 正在卖第 48 张门票
140100915386112 正在卖第 47 张门票
140100915386112 正在卖第 46 张门票
140100915386112 正在卖第 45 张门票
140100915386112 正在卖第 44 张门票
140100915386112 正在卖第 43 张门票
140100915386112 正在卖第 42 张门票
140100915386112 正在卖第 41 张门票
140100915386112 正在卖第 40 张门票
140100915386112 正在卖第 39 张门票
140100915386112 正在卖第 38 张门票
140100915386112 正在卖第 37 张门票
140100915386112 正在卖第 36 张门票
140100915386112 正在卖第 35 张门票
140100915386112 正在卖第 34 张门票
140100915386112 正在卖第 33 张门票
140100915386112 正在卖第 32 张门票
140100915386112 正在卖第 31 张门票
140100915386112 正在卖第 30 张门票
140100915386112 正在卖第 29 张门票
140100915386112 正在卖第 28 张门票
140100915386112 正在卖第 27 张门票
140100915386112 正在卖第 26 张门票
140100915386112 正在卖第 25 张门票
140100915386112 正在卖第 24 张门票
140100898600704 正在卖第 23 张门票
140100898600704 正在卖第 22 张门票
140100898600704 正在卖第 21 张门票
140100898600704 正在卖第 20 张门票
140100898600704 正在卖第 19 张门票
140100898600704 正在卖第 18 张门票
140100898600704 正在卖第 17 张门票
140100898600704 正在卖第 16 张门票
140100898600704 正在卖第 15 张门票
140100898600704 正在卖第 14 张门票
140100898600704 正在卖第 13 张门票
140100898600704 正在卖第 12 张门票
140100898600704 正在卖第 11 张门票
140100898600704 正在卖第 10 张门票
140100898600704 正在卖第 9 张门票
140100898600704 正在卖第 8 张门票
140100898600704 正在卖第 7 张门票
140100898600704 正在卖第 6 张门票
140100898600704 正在卖第 5 张门票
140100898600704 正在卖第 4 张门票
140100898600704 正在卖第 3 张门票
140100898600704 正在卖第 2 张门票
140100898600704 正在卖第 1 张门票

死锁

死锁的常见场景

  1. 忘记释放锁
    • 描述:一个线程在获取锁之后,由于程序逻辑的问题,忘记释放锁,导致其他线程无限期地等待该锁。
    • 预防:确保每个pthread_mutex_lock调用都在函数结束前有对应的pthread_mutex_unlock调用,无论是通过正常路径还是错误处理路径。
  2. 重复加锁
    • 描述:一个线程试图对同一个非递归锁加锁多次,导致该线程自身陷入等待状态。
    • 预防:避免设计需要重复加锁同一互斥量的逻辑,或使用递归互斥量(允许同一线程多次加锁)。
  3. 多线程多锁
    • 描述:多个线程以不同的顺序获取多个互斥量,导致相互等待对方释放锁。
    • 预防:设计一个固定的锁获取顺序,所有线程必须按此顺序获取锁。例如,总是先锁定互斥量A,然后是互斥量B。

如何解决和预防死锁

  1. 锁的顺序获取
    • 一致的锁获取顺序可以防止环形等待条件的发生,这是死锁的四个必要条件之一。
  2. 检测并避免
    • 在设计时进行仔细的代码审核和测试,以检测可能的死锁情况。使用工具如Valgrind中的Helgrind,或使用静态分析工具来识别死锁风险。
  3. 超时机制
    • 使用带超时的锁获取尝试,如pthread_mutex_trylock或者使用具备超时功能的同步原语,如pthread_mutex_timedlock。如果锁在指定时间内未能获取,线程可以解锁已持有的其他锁,然后重试或回滚操作。
  4. 资源分配图
    • 在复杂的应用中,创建资源分配图来分析资源分配和锁定模式,从而识别潜在的死锁。
  5. 使用锁层次结构
    • 通过定义每个锁的层次,并且在代码中严格按层次获取锁,可以有效防止死锁。
案例1
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>// 全局变量,所有的线程都共享这一份资源。
int tickets = 1000;// 创建一个互斥量
pthread_mutex_t mutex;void * sellticket(void * arg) {// 卖票while(1) {// 加锁pthread_mutex_lock(&mutex);pthread_mutex_lock(&mutex);if(tickets > 0) {usleep(6000);printf("%ld 正在卖第 %d 张门票\n", pthread_self(), tickets);tickets--;}else {// 解锁pthread_mutex_unlock(&mutex);break;}// 解锁pthread_mutex_unlock(&mutex);pthread_mutex_unlock(&mutex);}return NULL;
}int main() {// 初始化互斥量pthread_mutex_init(&mutex, NULL);// 创建3个子线程pthread_t tid1, tid2, tid3;pthread_create(&tid1, NULL, sellticket, NULL);pthread_create(&tid2, NULL, sellticket, NULL);pthread_create(&tid3, NULL, sellticket, NULL);// 回收子线程的资源,阻塞pthread_join(tid1, NULL);pthread_join(tid2, NULL);pthread_join(tid3, NULL);pthread_exit(NULL); // 退出主线程// 释放互斥量资源pthread_mutex_destroy(&mutex);return 0;
}
案例2
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>// 创建2个互斥量
pthread_mutex_t mutex1, mutex2;void * workA(void * arg) {pthread_mutex_lock(&mutex1);sleep(1); pthread_mutex_lock(&mutex2);printf("workA....\n");pthread_mutex_unlock(&mutex2);pthread_mutex_unlock(&mutex1);return NULL;
}void * workB(void * arg) {pthread_mutex_lock(&mutex2);sleep(1);pthread_mutex_lock(&mutex1);printf("workB....\n");pthread_mutex_unlock(&mutex1);pthread_mutex_unlock(&mutex2);return NULL;
}int main() {// 初始化互斥量pthread_mutex_init(&mutex1, NULL);pthread_mutex_init(&mutex2, NULL);// 创建2个子线程pthread_t tid1, tid2;pthread_create(&tid1, NULL, workA, NULL);pthread_create(&tid2, NULL, workB, NULL);// 回收子线程资源pthread_join(tid1, NULL);pthread_join(tid2, NULL);// 释放互斥量资源pthread_mutex_destroy(&mutex1);pthread_mutex_destroy(&mutex2);return 0;
}

读写锁

为什么在某些应用中使用传统的互斥锁(mutex)可能不是最优选择,特别是在读操作远多于写操作的场合。这种场合下,读写锁(Read-Write Locks)或共享-独占锁(shared-exclusive locks)就显得非常有用。

读写锁的概念和特性

读写锁允许多个线程同时读共享资源,但保证只有一个线程可以写。它们解决了互斥锁在读多写少的应用场景中引起的性能瓶颈。主要特点包括:

  • 共享读:当没有线程持有读写锁用于写入时,多个线程可以同时持有读写锁用于读取。
  • 独占写:写操作需要独占访问,即当一个线程获取读写锁进行写操作时,其他线程无论是读还是写都必须等待。
  • 写优先:通常实现读写锁时会给写操作以较高的优先级,这防止写操作饥饿(长时间等待),尤其是在读操作非常频繁的情况下。

读写锁相关函数

读写锁的类型 pthread_rwlock_t

你提供的函数是 POSIX 线程(Pthreads)库中关于读写锁的操作函数。读写锁允许多个线程同时读取同一个共享资源,但在写入时要求独占访问。以下是各个函数的详细说明和用法:

pthread_rwlock_init
  • 功能:初始化一个读写锁。

  • 原型

    int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
    
  • 参数

    • rwlock:指向将要初始化的读写锁的指针。
    • attr:指向读写锁属性的指针,可以设为 NULL 以使用默认属性。
  • 返回值:成功返回0;出错返回错误码。

pthread_rwlock_destroy
  • 功能:销毁一个读写锁。

  • 原型

    int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
    
  • 参数

    • rwlock:指向需要销毁的读写锁的指针。
  • 返回值:成功返回0;出错返回错误码。

pthread_rwlock_rdlock
  • 功能:以读模式锁定读写锁。

  • 原型

    int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
    
  • 参数

    • rwlock:指向要锁定的读写锁的指针。
  • 返回值:成功返回0;出错返回错误码。

pthread_rwlock_tryrdlock
  • 功能:尝试以读模式锁定读写锁。

  • 原型

    int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
    
  • 参数

    • rwlock:指向要尝试锁定的读写锁的指针。
  • 返回值:成功返回0;如果锁已经被其他线程以写模式锁定,返回EBUSY;其他错误返回相应的错误码。

pthread_rwlock_wrlock
  • 功能:以写模式锁定读写锁。

  • 原型

    int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
    
  • 参数

    • rwlock:指向要锁定的读写锁的指针。
  • 返回值:成功返回0;出错返回错误码。

pthread_rwlock_trywrlock
  • 功能:尝试以写模式锁定读写锁。

  • 原型

    int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
    
  • 参数

    • rwlock:指向要尝试锁定的读写锁的指针。
  • 返回值:成功返回0;如果锁已经被其他线程以读模式或写模式锁定,返回EBUSY;其他错误返回相应的错误码。

pthread_rwlock_unlock
  • 功能:解锁读写锁。

  • 原型

    int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
    
  • 参数

    • rwlock:指向要解锁的读写锁的指针。
  • 返回值:成功返回0;出错返回错误码。

使用示例

下面是一个使用读写锁的简单示例,展示了如何在多个线程中使用读写锁来同步对共享资源的访问:

#include <pthread.h>
#include <stdio.h>pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
int shared_data = 0;void* reader_function(void* arg) {pthread_rwlock_rdlock(&rwlock);printf("Reader: shared_data = %d\n", shared_data);pthread_rwlock_unlock(&rwlock);return NULL;
}void* writer_function(void* arg) {pthread_rwlock_wrlock(&rwlock);shared_data++;printf("Writer: incremented shared_data to %d\n", shared_data);pthread_rwlock_unlock(&rwlock);return NULL;
}int main() {pthread_t reader_thread, writer_thread;pthread_rwlock_init(&rwlock, NULL);pthread_create(&reader_thread, NULL, reader_function, NULL);pthread_create(&writer_thread, NULL, writer_function, NULL);pthread_join(reader_thread, NULL);pthread_join(writer_thread, NULL);pthread_rwlock_destroy(&rwlock);return 0;
}

这个示例展示了如何初始化、使用和销毁读写锁,并说明了如何在读者和写者之间同步访问共享数据。通过使用读写锁,可以有效提升程序在多线程环境下处理读多写少情况的性能。

案例
/*读写锁的类型 pthread_rwlock_tint pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);案例:8个线程操作同一个全局变量。3个线程不定时写这个全局变量,5个线程不定时的读这个全局变量
*/#include <stdio.h>
#include <pthread.h>
#include <unistd.h>// 创建一个共享数据
int num = 1;
// pthread_mutex_t mutex;
pthread_rwlock_t rwlock;void * writeNum(void * arg) {while(1) {pthread_rwlock_wrlock(&rwlock);num++;printf("++write, tid : %ld, num : %d\n", pthread_self(), num);pthread_rwlock_unlock(&rwlock);usleep(100);}return NULL;
}void * readNum(void * arg) {while(1) {pthread_rwlock_rdlock(&rwlock);printf("===read, tid : %ld, num : %d\n", pthread_self(), num);pthread_rwlock_unlock(&rwlock);usleep(100);}return NULL;
}int main() {pthread_rwlock_init(&rwlock, NULL);// 创建3个写线程,5个读线程(线程池)pthread_t wtids[3], rtids[5];for(int i = 0; i < 3; i++) {pthread_create(&wtids[i], NULL, writeNum, NULL);}for(int i = 0; i < 5; i++) {pthread_create(&rtids[i], NULL, readNum, NULL);}// 设置线程分离for(int i = 0; i < 3; i++) {pthread_detach(wtids[i]);}for(int i = 0; i < 5; i++) {pthread_detach(rtids[i]);}pthread_exit(NULL);pthread_rwlock_destroy(&rwlock);return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/9947.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

jenkins部署想定报错

报错&#xff1a; 解决办法&#xff1a; 登录被编译的设备&#xff0c;清楚旧代码&#xff0c;在重新执行

Dependencies:查找项目中dll关联文件是否缺失。

前言 Dependencies工具作为一款优秀的DLL解析工具&#xff0c;能让你很直观地看到DLL的相关信息&#xff0c;如具备哪些功能函数、参数&#xff0c;又比如该DLL基于哪些DLL运行。判断该dll基于哪些dll运行&#xff0c;如果基于的dll丢失&#xff0c;那么就会提示。就能判断缺少…

【Ubuntu永久授权串口设备读取权限‘/dev/ttyUSB0‘】

Ubuntu永久授权串口设备读取权限 1 问题描述2 解决方案2.1 查看ttyUSB0权限&#xff0c;拥有者是root&#xff0c;所属用户组为dialout2.2 查看dialout用户组成员&#xff0c;如图所示&#xff0c;普通用户y不在dialout组中2.3 将普通用户y加入dialout组中2.4 再次查看dialout用…

Redis-新数据类型-Hyperloglog

新数据类型-Hyperloglog 简介 在我们做站点流量统计的时候一般会统计页面UV(独立访客:unique visitor)和PV(即页面浏览量&#xff1a;page view)。 什么是基数&#xff1f; 数据集&#xff5b;1&#xff0c;2&#xff0c;5&#xff0c;7&#xff0c;5&#xff0c;7&#xff…

AI绘画的基本原理是什么?

目录 一、AI绘画的基本原理是什么&#xff1f; 二、Python中有几个库可以用于AI绘画&#xff1f; 三、OpenCV画一个人形 四、AI画的红苹果 一、AI绘画的基本原理是什么&#xff1f; AI绘画的原理基于机器学习和人工智能技术&#xff0c;通过这些技术模型能够理解文本描述并…

vivado 配置存储器支持-Artix-7 配置存储器器件

配置存储器支持 本章主要讲解 Vivado 软件支持的各种非易失性器件存储器。请使用本章作为指南 &#xff0c; 按赛灵思系列、接口、制造商、 密度和数据宽度来为您的应用选择适用的配置存储器器件。 Artix-7 配置存储器器件 下表所示闪存器件支持通过 Vivado 软件对 A…

HTTP 1.1 与 HTTP 1.0

什么是HTTP HTTP 就是一个 超文本传输协议 协议 : 双方 约定 发送的 域名 数据长度 连接(长连接还是短连接) 格式(UTF-8那些) 传输 :数据虽然是在 A 和 B 之间传输&#xff0c;但允许中间有中转或接力。 超文本:图片、视频、压缩包,在HTTP里都是文本 HTTP 常见状态码 比如 20…

在go-zero中使用jwt

gozero使用jwt 两个步骤 获取token验证token 前端获取token 先编写 jwt.api 文件&#xff0c;放在api目录下 syntax "v1"info (title: "type title here"desc: "type desc here"author: "type author here"email: &quo…

工控组态技术:实现工业自动化控制的重要手段

体验地址&#xff1a;by组态[web组态插件] 工控组态技术是一种应用于工业自动化控制领域的重要技术&#xff0c;它通过将各种不同的硬件设备和软件系统进行组合和配置&#xff0c;实现了工业生产过程的自动化控制和优化。 随着工业技术的不断发展和进步&#xff0c;工控组态技…

拥有蝴蝶效应的爬虫如何进行防护

美国气象学家爱德华罗伦兹&#xff08;Edward N.Lorenz&#xff09;1963年在一篇提交纽约科学院的论文中分析了一个叫做蝴蝶效应的理论&#xff1a;“一个气象学家提及&#xff0c;如果这个理论被证明正确&#xff0c;一只海鸥扇动翅膀足以永远改变天气变化。”在以后的演讲和论…

IP 地理定位神话与事实

ip地理定位是一项技术&#xff0c;用于通过访问设备的ip地址来获取地理位置信息&#xff0c;例如国家、城市、经纬度等。该技术广泛应用于网站内容自定义、广告定位、网络安全和用户分析等领域。它通过与包含ip地址和地理位置映射的大型数据库进行查询来工作&#xff0c;但在准…

软件测评报告:除了软件测评中心,还有哪些选择?

传统的观念中&#xff0c;软件测评中心往往被视为进行软件测评的首选机构。然而&#xff0c;随着技术的发展和市场的扩大&#xff0c;除了软件测评中心&#xff0c;越来越多的机构和平台也提供了专业的软件测评服务。本文将探讨除了软件测评中心之外&#xff0c;还有哪些地方可…

工作中使用Optional处理空指针异常

工作中使用Optional处理空指针异常 实体类以前对空指针的判断Optional处理空指针测试结果 实体类 package po;import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor;import java.io.Serializable;Data AllArgsConstructor NoArgsConstruct…

单调栈:(C++)

在题目的要求中&#xff0c;存在先进后出&#xff08;即在前面的数据需要遍历到后面的某一数据时才能确定计算值&#xff09;单调栈在一部分解题场景中避免了暴力解法的高时间复杂度问题&#xff0c;但是在做题过程中视情况而定&#xff0c;有些题目的最优解不一定使用单调栈&a…

2024下载旧版本谷歌浏览器和谷歌驱动器chromedriver,亲测有效

2024下载旧版本谷歌浏览器和谷歌驱动器chromedriver,亲测有效 1. 下载旧版本谷歌浏览器 找了很多博客&#xff0c;实验了很多种&#xff0c;我发现最有效的是下面的网址&#xff0c;可能需要一些科技。 但是下载下来的谷歌浏览器版本是ok的。拿来就能用&#xff0c;亲测有效…

QT创造一个新的类(柱状图的类),并关联属性和方法

1.以在UI上添加柱状图的类为例&#xff08;Histogram&#xff09; #ifndef STUDY_HISTOGRAM_H #define STUDY_HISTOGRAM_H#include <QVector> #include <QWidget>// 前向声明 QT_BEGIN_NAMESPACE class QColor; class QRect; class QString; class QPaintDevice; …

【机器学习300问】84、AdaGrad算法是为了解决什么问题?

神经网络的学习的目的是找到使损失函数的值尽可能小的参数。这是寻找最优参数的问题&#xff0c;解决这个问题的过程称为最优化。因为参数空间非常复杂&#xff0c;无法轻易找到最优解&#xff0c;而且在深度神经网络中&#xff0c;参数的数量非常庞大&#xff0c;导致最优化问…

【SpringBoot篇】基于Redis分布式锁的 误删问题 和 原子性问题

文章目录 &#x1f354;Redis的分布式锁&#x1f6f8;误删问题&#x1f388;解决方法&#x1f50e;代码实现 &#x1f6f8;原子性问题&#x1f339;Lua脚本 ⭐利用Java代码调用Lua脚本改造分布式锁&#x1f50e;代码实现 &#x1f354;Redis的分布式锁 Redis的分布式锁是通过利…

基于FPGA的音视频监视器,音视频接口采集器的应用

① 支持1路HDMI1路SDI 输入 ② 支持1路HDMI输出 ③ 支持1080P高清屏显示实时画面以 及叠加的分析结果 ④ 支持同时查看波形图&#xff08;亮度/RGB&#xff09;、 直方图、矢量图 ⑤ 支持峰值对焦、斑马纹、伪彩色、 单色、安全框遮幅标记 ⑥ 支持任意缩放画面&#xff0c;支…

远程桌面连接不上怎么连服务器,原因是什么?如何解决?

远程桌面连接不上怎么连服务器&#xff0c;原因是什么&#xff1f;如何解决&#xff1f; 面对远程桌面连接不上的困境&#xff0c;我们有办法&#xff01; 当你尝试通过远程桌面连接服务器&#xff0c;但遭遇连接失败的挫折时&#xff0c;不要慌张。这种情况可能由多种原因引起…