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

news2024/10/5 13:04:54

线程同步

基本概念

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

临界区(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_t
    int 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_t
    int 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.coloradmin.cn/o/1680797.html

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

相关文章

【C语言】指针(二)

目录 一、传值调用和传址调用 二、数组名的理解 三、通过指针访问数组 四、一维数组传参的本质 五、指针数组 六、指针数组模拟实现二维数组 一、传值调用和传址调用 指针可以用在哪里呢&#xff1f;我们看下面一段代码&#xff1a; #include <stdio.h>void Swap(i…

【Week Y9】yolo-v8网络结构的主要模块学习

文章目录 一、...\ultralytics-main\ultralytics\nn\modules\conv.py&#xff1a;&#xff08;1&#xff09;__all__说明&#xff1a;&#xff08;2&#xff09;autopad()说明&#xff1a;&#xff08;3&#xff09;conv()说明&#xff1a;&#xff08;4&#xff09;Focus()说明…

ue引擎游戏开发笔记(40)——行为树的建立:丰富ai行动

1.需求分析&#xff1a; 敌人的ai行为随着开发的不断更新&#xff0c;会不断增加&#xff0c;如果每次都进入c中编写会很不方便&#xff0c;也无法凸显ue引擎中行为树的优势作用&#xff0c;因此有必要将敌人的ai行为&#xff0c;全部转到行为树中实现。 2.操作实现&#xff1…

R实验 基础(二)

实验目的&#xff1a; 掌握向量的几种类型&#xff1a;数值向量、逻辑向量、字符向量、复数向量&#xff1b;掌握生成向量几个的函数使用和向量的下标运算&#xff1b;掌握因子的定义和相关函数的使用。 实验内容&#xff1a; R语言中&#xff0c;数值向量用得非常多。生成数…

深入解析Wireshark1:从捕获到分析,一网打尽数据包之旅

目录 1 认识 Wireshark 1.1 选择网卡界面 1.2 捕获数据包界面 1.3 常用按钮功能介绍 1.4 数据包列表信息 1.5 数据包详细信息 2 数据包案例分析 Frame: 物理层的数据帧概况 Ethernet II: 数据链路层以太网帧头部信息 Internet Protocol Version 4 (IPv4): 互联网层IP…

【Python】图形用户界面设计

1、设计并编写一个窗口程序,该窗口只有一个按钮,当用户单击时可在后台输出hello world. import tkinter as tk def on_button_click():print("hello world") # 创建主窗口 root tk.Tk() root.title("Hello World Button") # 设置窗口大小 root.geometry…

2005-2022年全国及各省绿色信贷水平测算数据(含原始数据+计算过程+计算结果)

2005-2022年全国及各省绿色信贷水平测算数据&#xff08;含原始数据计算过程计算结果&#xff09; 1、时间&#xff1a;2005-2022年 2、来源&#xff1a;工业统计年鉴、统计年鉴、其中2017年采用插值法填补 3、范围&#xff1a;31省 4、方法说明&#xff1a;选取各省六大高…

每日一题13:Pandas:方法链

一、每日一题 &#xff1b;&#xff1a;&#xff1a; 解答&#xff1a; import pandas as pddef findHeavyAnimals(animals: pd.DataFrame) -> pd.DataFrame:heavy_animals animals[animals[weight] > 100].sort_values(byweight, ascendingFalse)result heavy_anim…

代码随想录--链表--反转链表

题目 题意&#xff1a;反转一个单链表。 示例: 输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL 思路 如果再定义一个新的链表&#xff0c;实现链表元素的反转&#xff0c;其实这是对内存空间的浪费。 其实只需要改变链表的next指针的…

Java项目:基于ssm框架实现的家政服务网站管理系统分前后台(B/S架构+源码+数据库+毕业论文+答辩PPT)

一、项目简介 本项目是一套基于ssm框架实现的家政服务网站管理系统 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xff0c;eclipse或者idea 确保可以运行&#xff01; 二、技术实现 jdk版本&#xff1a;1.…

Qt+C++串口调试工具

程序示例精选 QtC串口调试工具 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对《QtC串口调试工具》编写代码&#xff0c;代码整洁&#xff0c;规则&#xff0c;易读。 学习与应用推荐首选。 …

JWT生成token工具类实现

JWT简介 JWT定义 JWT全称为Json web token&#xff0c;也就是 Json 格式的 web token JWT数据结构 1.JWT由三段字符串组成&#xff0c;中间用.分隔 Project_eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIiwiZXhwIjoxNzE2MzcwMTM0LCJpYXQiOjE3MTU3NjUzMzQsImp0aSI6IjllO…

关于DOCKER启动后如何添加新的端口映射

前段时间在用docker部署服务的时候发现&#xff0c;容器已经启动&#xff0c;但是需要新的端口映射&#xff08;即容器在启动的时候只进行了部分的端口映射&#xff09;&#xff0c;经过查询资料后发现现在网上有2种方法&#xff0c;一中是修改json文件。另一种是将已经运行的容…

QT:QML中读取文件(QDesktopServices和QFile)

目录 一.介绍 二.QDesktopServices: 1.添加头文件 2.声明函数 3.操作 4.注册 5.qml调用 三.QFile&#xff1a; 1.添加头文件 2.声明函数 3.读取指定文件名的文件内容 4.注册 5.qml中调用 四.效果展示&#xff1a; 1.QDesktopServices&#xff1a;上方按钮点击打开…

纯血鸿蒙APP实战开发——Web获取相机拍照图片案例

介绍 本示例介绍如何在HTML页面中拉起原生相机进行拍照&#xff0c;并获取返回的图片。 效果预览图 使用说明 点击HTML页面中的选择文件按钮&#xff0c;拉起原生相机进行拍照。完成拍照后&#xff0c;将图片在HTML的img标签中显示。 实现思路 添加Web组件&#xff0c;设置…

一看就会的AOP事务

文章目录 AOPAOP简介AOP简介和作用AOP的应用场景为什么要学习AOP AOP入门案例思路分析代码实现AOP中的核心概念 AOP工作流程AOP工作流程AOP核心概念在测试类中验证代理对象 AOP切入点表达式语法格式通配符书写技巧 AOP通知类型AOP通知分类AOP通知详解 AOP案例案例-测量业务层接…

太极图形学——高级数据结构——稠密

太极是一个面向数据的编程语言 在并行计算的框架下&#xff0c;在计算上花费的时间反而是少数&#xff0c;大量的时间都花在了数据获取&#xff08;也就是访问内存&#xff09;上面&#xff0c;这一点在之前的games103课程上也有简单的提及 cpu的计算能力非常强大&#xff0c…

Qwen学习笔记3:Qwen模型调用外部API实现模型增强(openai的形式)

前言 本文记录了使用本地部署的Qwen模型&#xff0c;调用外部API实现模型的功能增强&#xff0c;非常的易用&#xff0c;大家用于开发自己的应用&#xff0c;只需要作简单的修改就可以进行使用了。 本文的代码来源视频教程&#xff1a; Qwen大模型变强了&#xff0c;通过API…

【SQL】SQL常见面试题总结(2)

目录 1、增删改操作1.1、插入记录&#xff08;一&#xff09;1.2、插入记录&#xff08;二&#xff09;1.3、插入记录&#xff08;三&#xff09;1.4、更新记录&#xff08;一&#xff09;1.5、更新记录&#xff08;二&#xff09;1.6、删除记录&#xff08;一&#xff09;1.7、…

【文末附gpt升级方案】腾讯混元文生图大模型开源:中文原生Sora同款DiT架构引领新潮流

在人工智能与计算机视觉技术迅猛发展的今天&#xff0c;腾讯再次引领行业潮流&#xff0c;宣布其旗下的混元文生图大模型全面升级并对外开源。这次开源的模型不仅具备强大的文生图能力&#xff0c;更采用了业内首个中文原生的Sora同款DiT架构&#xff0c;为中文世界的视觉生成领…