当前位置: 首页 > news >正文

互斥锁(mutex) ---- 静态锁与动态锁

在多线程编程中,互斥锁(mutex)是确保共享资源安全访问的重要机制。POSIX线程(pthread)提供了两种方式来初始化互斥锁:静态初始化和动态初始化。理解这两种方式的区别和适用场景,对于编写高效、安全的多线程程序至关重要。

目录

一、静态初始化互斥锁

1. 定义与初始化

2. 优点

3. 缺点

4. 适用场景

5. 示例代码

二、动态初始化互斥锁

1. 定义与初始化

2. 优点

3. 缺点

4. 适用场景

5. 示例代码

三、选择合适的初始化方式

四、总结

五、题外话----pthread_mutex_init再探究

步骤 1:初始化属性对象

步骤 2:设置属性

步骤 3:动态初始化互斥锁

步骤 4:销毁属性对象

互斥锁的类型

1. 普通互斥锁(Default)

2. 递归互斥锁(Recursive)

3. 错误检查互斥锁(Error Checking)

4. 示例代码:使用递归互斥锁

总的来说



一、静态初始化互斥锁

1. 定义与初始化

静态初始化是通过宏PTHREAD_MUTEX_INITIALIZER来完成的。例如:

pthread_mutex_t my_mutex = PTHREAD_MUTEX_INITIALIZER;

这行代码声明并初始化了一个互斥锁my_mutex。静态初始化的互斥锁在程序启动时就已经被初始化,不需要额外的函数调用。

2. 优点
  • 简单易用:只需一行代码即可完成定义和初始化。
  • 性能稍优:由于初始化是在编译时完成的,运行时开销较小。
3. 缺点
  • 不可销毁:静态初始化的互斥锁不能通过pthread_mutex_destroy函数进行销毁。它们的生命周期与程序的生命周期一致。
  • 灵活性有限:无法通过属性参数定制互斥锁的行为,例如设置递归锁或错误检查锁。
4. 适用场景
  • 全局或静态变量:适用于那些在整个程序运行期间都需要存在的互斥锁。
  • 简单程序:对于不需要动态管理互斥锁的简单程序,静态初始化是一个方便的选择。
5. 示例代码
#include <pthread.h>
#include <stdio.h>
pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;
int counter = 0;
void *thread_increment(void *arg)
{
    for (int i = 0; i < 1000; i++)
    {
        pthread_mutex_lock(&counter_mutex);
        counter++;
        pthread_mutex_unlock(&counter_mutex);
    }
    return NULL;
}
int main()
{
    pthread_t threads[10];
    for (int i = 0; i < 10; i++)
    {
        pthread_create(&threads[i], NULL, thread_increment, NULL);
    }
    for (int i = 0; i < 10; i++)
    {
        pthread_join(threads[i], NULL);
    }
    printf("Final counter: %d\n", counter);
    return 0;
}

在这个示例中,静态初始化的互斥锁counter_mutex被用来保护对共享计数器counter的递增操作。程序运行后,最终的计数器值是正确的,说明静态初始化的互斥锁有效地保护了共享资源。


二、动态初始化互斥锁

1. 定义与初始化

动态初始化是通过调用pthread_mutex_init函数来完成的。例如:

pthread_mutex_t my_mutex; pthread_mutex_init(&my_mutex, NULL);

这行代码首先声明了一个互斥锁my_mutex,然后通过pthread_mutex_init函数对其进行初始化。NULL作为属性参数,表示使用默认的互斥锁属性。

2. 优点
  • 可销毁:动态初始化的互斥锁可以通过pthread_mutex_destroy函数进行销毁,释放相关资源。
  • 灵活性高:可以通过属性参数定制互斥锁的行为,例如设置递归锁或错误检查锁。
3. 缺点
  • 初始化开销:需要在运行时调用pthread_mutex_init函数进行初始化,增加了运行时开销。
  • 复杂性较高:需要额外的代码来管理互斥锁的生命周期。
4. 适用场景
  • 动态分配的资源:适用于那些需要在运行时创建和销毁的互斥锁。
  • 需要定制互斥锁行为的场景:例如,需要使用递归锁或错误检查锁时。
5. 示例代码
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
typedef struct
{
    pthread_mutex_t lock;
    int *array;
    int size;
} Array;
void initialize_array(Array *arr, int size)
{
    arr->array = malloc(size * sizeof(int));
    arr->size = size;
    pthread_mutex_init(&arr->lock, NULL);
}
void destroy_array(Array *arr)
{
    pthread_mutex_destroy(&arr->lock);
    free(arr->array);
}
void *thread_increment(void *arg)
{
    Array *arr = (Array *)arg;
    for (int i = 0; i < arr->size; i++)
    {
        pthread_mutex_lock(&arr->lock);
        arr->array[i]++;
        pthread_mutex_unlock(&arr->lock);
    }
    return NULL;
}
int main()
{
    Array my_array;
    initialize_array(&my_array, 100);
    pthread_t threads[10];
    for (int i = 0; i < 10; i++)
    {
        pthread_create(&threads[i], NULL, thread_increment, &my_array);
    }
    for (int i = 0; i < 10; i++)
    {
        pthread_join(threads[i], NULL);
    }
    for (int i = 0; i < my_array.size; i++)
    {
        printf("Array[%d] = %d\n", i, my_array.array[i]);
    }
    destroy_array(&my_array);
    return 0;
}

在这个示例中,动态初始化的互斥锁arr->lock被用来保护对动态分配的数组arr->array的访问。程序运行后,数组中的每个元素都被正确递增,说明动态初始化的互斥锁同样有效地保护了共享资源。


三、选择合适的初始化方式

在实际应用中,选择使用静态还是动态初始化的互斥锁,需要根据具体的场景和需求来决定:

  • 静态初始化:适用于全局或静态变量的初始化,使用简单且性能稍优。但不可销毁,灵活性有限。
  • 动态初始化:适用于需要动态管理互斥锁生命周期,或需要定制互斥锁行为的场景。虽然初始化开销稍高,但提供了更大的灵活性和控制能力。

四、总结

静态初始化和动态初始化互斥锁是POSIX线程提供的两种不同的初始化方式,各有优缺点。理解它们的区别和适用场景,有助于编写更高效、更安全的多线程程序。

  • 静态初始化:简单易用,适用于全局或静态变量。
  • 动态初始化:灵活可销毁,适用于动态管理资源的场景。

根据实际需求选择合适的初始化方式,可以更好地利用互斥锁来保护共享资源,避免竞态条件和数据损坏。

五、题外话----pthread_mutex_init再探究

在动态初始化互斥锁时,pthread_mutex_init函数的第二个参数attr是一个指向pthread_mutexattr_t结构体的指针。该结构体用于设置互斥锁的属性。NULL表示使用默认属性,但也可以传入一个已初始化的pthread_mutexattr_t指针,以定制互斥锁的行为。

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);

首先,pthread_mutexattr_t是一个结构体,它包含了互斥锁的各种属性。这些属性包括:

  1. 类型(Type):决定互斥锁是普通锁、递归锁还是错误检查锁。
  2. 协议(Protocol):用于实现互斥锁的协议。
  3. 继承(Inheritance):决定子线程是否继承父线程的互斥锁属性。

接下来,我需要解释如何创建和设置一个pthread_mutexattr_t对象。步骤如下:

  1. 初始化属性对象:使用pthread_mutexattr_init函数初始化一个属性对象。
  2. 设置属性:使用相应的函数(如pthread_mutexattr_settype)来设置互斥锁的类型。
  3. 动态初始化互斥锁:将设置好的属性对象传入pthread_mutex_init函数。
  4. 销毁属性对象:使用pthread_mutexattr_destroy函数销毁属性对象。

要使用自定义属性初始化互斥锁,需要按照以下步骤进行:

步骤 1:初始化属性对象
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);

这行代码初始化了一个互斥锁属性对象attr,使其具有默认属性。

步骤 2:设置属性

根据需要,调用相应的函数设置属性。例如,设置互斥锁为递归锁:

pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
步骤 3:动态初始化互斥锁

将设置好的属性对象传入pthread_mutex_init函数:

pthread_mutex_t my_mutex; 
pthread_mutex_init(&my_mutex, &attr);
步骤 4:销毁属性对象

在不再需要属性对象时,调用以下函数销毁它:

pthread_mutexattr_destroy(&attr);

互斥锁的类型

通过pthread_mutexattr_settype函数,可以设置互斥锁的类型。以下是常见的类型:

1. 普通互斥锁(Default)
  • 默认类型PTHREAD_MUTEX_DEFAULT
  • 行为:非递归锁。同一个线程不能多次锁定同一个互斥锁,否则可能导致死锁。
  • 适用场景:大多数情况下,普通互斥锁已经足够。
2. 递归互斥锁(Recursive)
  • 类型PTHREAD_MUTEX_RECURSIVE
  • 行为:允许同一个线程多次锁定同一个互斥锁。每次加锁必须对应一次解锁,线程退出时自动释放所有锁。
  • 适用场景:当一个线程需要多次访问受保护的资源时,递归互斥锁非常有用。
3. 错误检查互斥锁(Error Checking)
  • 类型PTHREAD_MUTEX_ERRORCHECK
  • 行为:如果一个线程尝试递归锁定一个普通互斥锁,或者在解锁时互斥锁未被锁定,会返回错误码EDEADLK
  • 适用场景:需要严格检查互斥锁使用错误的场景。

4. 示例代码:使用递归互斥锁

以下是一个使用递归互斥锁的示例:

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
pthread_mutex_t lock;
pthread_mutexattr_t attr;
void *thread_function(void *arg)
{
    int *count = (int *)arg;
    for (int i = 0; i < *count; i++)
    {
        pthread_mutex_lock(&lock);
        printf("Thread %ld: Count = %d\n", pthread_self(), i);
        pthread_mutex_unlock(&lock);
    }
    return NULL;
}
int main()
{
    // 初始化属性对象
    pthread_mutexattr_init(&attr);
    // 设置为递归互斥锁
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    // 初始化互斥锁
    pthread_mutex_init(&lock, &attr);
    // 创建线程
    pthread_t thread1, thread2;
    int count = 5;
    pthread_create(&thread1, NULL, thread_function, &count);
    pthread_create(&thread2, NULL, thread_function, &count);
    // 等待线程结束
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);
    // 销毁互斥锁
    pthread_mutex_destroy(&lock);
    // 销毁属性对象
    pthread_mutexattr_destroy(&attr);
    return 0;
}

在这个示例中,我们创建了一个递归互斥锁,允许同一个线程多次锁定和解锁。两个线程交替访问共享资源,确保输出有序。

总的来说
  • pthread_mutex_init函数的第二个参数attr可以传入NULL或一个已初始化的pthread_mutexattr_t指针。
  • 使用NULL表示使用默认属性(普通互斥锁)。
  • 通过设置attr可以定制互斥锁的行为,例如使用递归锁或错误检查锁。
  • 动态初始化互斥锁提供了更大的灵活性,适用于需要动态管理资源或定制行为的场景。

相关文章:

  • (C语言)算法复习总结1——二分查找
  • 【T2I】Region-Aware Text-to-Image Generation via Hard Binding and Soft Refinement
  • GPT-2 语言模型 - 模型训练
  • 关于柔性数组
  • 开源项目faster-whisper和whisper是啥关系
  • C语言之continue相关题目
  • 剖析 Rust 与 C++:性能、安全及实践对比
  • 【频域分析】对数谱
  • app逆向专题四:charles抓包工具配置
  • Relief法**是一种非常经典、有效的**特征选择算法
  • Java—— 文字版格斗游戏
  • 整型与布尔型的转换
  • 二分三分算法详解, 模板与临界条件分析
  • Android开发:应用DeepSeek官方Api在App中实现对话功能
  • 智能制造方案精读:117页MES制造执行系统解决方案【附全文阅读】
  • vue webSocket
  • 腾势品牌欧洲市场冲锋,科技豪华席卷米兰
  • CSI-PVController-claimWorker
  • 【Unity精品源码】Ultimate Character Controller:高级角色控制器完整解决方案
  • Go语言Slice切片底层
  • 财政部党组召开2025年巡视工作会议暨第一轮巡视动员部署会
  • 法学联合书单|法庭上的妇女
  • 国台办:80年前台湾重归中国版图,80年后不可能让台湾分裂出去
  • 排污染黑海水后用沙土覆盖黑泥?汕尾环保部门:非欲盖弥彰
  • 特朗普访中东绕行以色列,专家:凸显美以利益分歧扩大
  • 新剧|《藏海传》定档,《折腰》《人生若如初见》今日开播