线程同步之互斥量

文章目录

latex 复制代码
多线程共享全局变量 → 数据竞争问题
    ↓
需要同步机制 → 互斥量(Mutex)解决方案
    ↓
互斥量使用模式 → 初始化、加锁、解锁、销毁

全局变量同步问题

  • 在多线程程序中,如果多个线程同时对共享的全局变量进行读写操作,可能会出现数据竞争(Race Condition)问题,导致最终结果与预期不一致
c 复制代码
/* 为突出问题 代码省略了错误处理*/
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

static int glob;  // 全局变量,被所有线程共享

// 线程函数
void *ThreadFunc(void *arg) {
    int loops = *((int *)arg);  // 从参数中获取循环次数
    int loc, j;
    
    for(j = 0; j < loops; j++) {
        loc = glob;  // 读取全局变量
        loc++;       // 局部变量自增
        glob = loc;  // 将局部变量的值赋给全局变量
    }

    pthread_exit(NULL);  // 线程退出
}

int main(int argc, const char *argv[])
{
    pthread_t tid1, tid2;  // 定义两个线程 ID
    int loops;

    // 检查命令行参数
    if(argc != 2) {
        fprintf(stderr, "%s [loops]\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    // 从命令行参数中读取循环次数
    if( sscanf(argv[1], "%d", &loops) != 1) {
        fprintf(stderr, "Invalid loops\n");
        exit(EXIT_FAILURE);
    }

    // 创建两个线程,都执行 ThreadFunc 函数
    pthread_create(&tid1, NULL, ThreadFunc, &loops);
    pthread_create(&tid2, NULL, ThreadFunc, &loops);

    // 等待两个线程结束
    pthread_join(tid1, NULL); 
    pthread_join(tid2, NULL);

    // 打印最终的全局变量值
    printf("glob = %d\n", glob);
    return 0;
}
  • 理论上,如果 loops 为 n,最终 glob 的值应该是 2n,但结果似乎并不总是这样
  • 两个线程同时执行上述操作时,可能会出现以下情况:
    • 线程A读取glob值(例如100)
    • 线程B也读取glob(仍为100)
    • 两个线程都执行loc++
    • 最终glob可能只增加一次,而非两次
  • 被称为非原子操作导致的同步问题

互斥量

  • 互斥量(Mutex)是一种锁机制,用于保护共享资源,确保同一时间只有一个线程能访问该资源
  • 属于pthread_mutex_t类型,需在使用前初始化
  • 合理使用互斥量可保证线程安全,提升程序稳定性

互斥量初始化方式

静态初始化(编译时)

  • 对于静态分配的互斥量而言,将PTHREAD_MUTEX_INITIALIZER赋给互斥量
  • 适用于全局变量或静态变量
  • 系统会自动初始化为解锁状态
c 复制代码
#include <pthread.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

静态初始化的方式等效于通过调用pthread_mutex_init()进行动态初始化,并将参数attr指定为NULL,但不会执行错误检查

动态初始化(运行时)

  • 适用于动态分配于堆或栈上的互斥量
    • 动态创建针对某一结构的链表,表中每个结构都包含一个pthread_mutex_t 类型的字段来存放互斥量,借以保护对该结构的访问
  • 需要自定义属性
c 复制代码
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
                       const pthread_mutexattr_t *restrict attr);
  • mutex:指定函数执行初始化操作的目标互斥量
  • attr:是指向pthread_mutexattr_t类型对象的指针,该对象在函数调用之前已经过了初始化处理,用于定义互斥量的属性。
    • 若将attr参数置为NULL,则该互斥量的各种属性会取默认值

互斥量操作函数

函数 说明
pthread_mutex_lock(&mutex) 加锁,若已被锁则阻塞
pthread_mutex_unlock(&mutex) 解锁
pthread_mutex_trylock(&mutex) 尝试加锁,失败立即返回
pthread_mutex_destroy(&mutex) 销毁互斥量

临界区(Critical Section)

c 复制代码
pthread_mutex_lock(&mutex);
// 临界区:访问共享资源的代码
glob++;
pthread_mutex_unlock(&mutex);

互斥量解决同步问题

  • 静态初始化
c 复制代码
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static int glob;  //临界资源
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 静态初始化互斥量

void *ThreadFunc(void *arg) {
    int loops = *((int *)arg);
    int loc, j;

    for(j = 0; j < loops; j++) {
        pthread_mutex_lock(&mutex);
        //加锁与解锁的代码区域被称为 临界区
        loc = glob;
        loc++;
        glob = loc;
        pthread_mutex_unlock(&mutex);
    }

    pthread_exit(NULL);
}

int main(int argc, const char *argv[])
{
    pthread_t tid1, tid2;
    int loops;

    if(argc != 2) {
        fprintf(stderr, "%s [loops]\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    if( sscanf(argv[1], "%d", &loops) != 1) {
        fprintf(stderr, "Invalid loops\n");
        exit(EXIT_FAILURE);
    }

    int ret = pthread_create(&tid1, NULL, ThreadFunc, &loops);
    if(ret != 0) {
        fprintf(stderr, "pthread_create:%s\n", strerror(ret));
        exit(EXIT_FAILURE);
    }
    ret = pthread_create(&tid2, NULL, ThreadFunc, &loops);
    if(ret != 0) {
        fprintf(stderr, "pthread_create:%s\n", strerror(ret));
        exit(EXIT_FAILURE);
    }

    ret = pthread_join(tid1, NULL); 
    if(ret != 0) {
        fprintf(stderr, "pthread_join tid1:%s\n", strerror(ret));
        exit(EXIT_FAILURE);
    }

    ret = pthread_join(tid2, NULL);
    if(ret != 0) {
        fprintf(stderr, "pthread_join tid2:%s\n", strerror(ret));
        exit(EXIT_FAILURE);
    }

    printf("glob = %d\n", glob);
    return 0;
}
  • 动态初始化
c 复制代码
/* 省略了错误处理*/
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

static int glob;

struct thread {
    int loops;
    pthread_mutex_t mutex;
};

// 线程函数
void *ThreadFunc(void *arg) {
    struct thread *data = (struct thread *)arg;  // 从参数中获取循环次数
    int loc, j;

    for(j = 0; j < data->loops; j++) {
        pthread_mutex_lock(&data->mutex);
        glob++;
        pthread_mutex_unlock(&data->mutex);
    }

    pthread_exit(NULL);  // 线程退出
}

int main(int argc, const char *argv[])
{
    pthread_t tid1, tid2;  // 定义两个线程 ID

    struct thread data;
    pthread_mutex_init(&data.mutex, NULL);

    // 检查命令行参数
    if(argc != 2) {
        fprintf(stderr, "%s [loops]\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    // 从命令行参数中读取循环次数
    if( sscanf(argv[1], "%d", &data.loops) != 1) {
        fprintf(stderr, "Invalid loops\n");
        exit(EXIT_FAILURE);
    }

    // 创建两个线程,都执行 ThreadFunc 函数
    pthread_create(&tid1, NULL, ThreadFunc, &data);
    pthread_create(&tid2, NULL, ThreadFunc, &data);

    // 等待两个线程结束
    pthread_join(tid1, NULL); 
    pthread_join(tid2, NULL);

    pthread_mutex_destroy(&data.mutex);
    // 打印最终的全局变量值
    printf("glob = %d\n", glob);
    return 0;
}

注意问题

  • 静态初始化用于全局/静态变量,动态初始化用于局部/动态变量
  • 不要重复初始化已初始化的互斥量
  • 不要在锁定的状态下销毁互斥量
  • 确保所有线程解锁后再销毁互斥量
  • 加锁与解锁必须成对出现,避免死锁
  • 临界区应尽量简短,避免长时间占用锁
  • **互斥锁无论读写都只能被一个线程持有。****读写锁的核心特点是读锁共享(多个线程可同时持有读锁)、写锁独占(仅一个线程可持有写锁)。**两者都需要初始化,性能取决于场景(读多写少场景读写锁更优)
相关推荐
烛衔溟12 小时前
C语言并发编程:Windows线程
c语言·c++·windows·性能优化·多线程·并发编程·线程同步
我在人间贩卖青春1 天前
线程同步之读写锁
线程同步·读写锁
我在人间贩卖青春1 天前
线程同步之条件变量
条件变量·线程同步·生产者与消费者·虚假唤醒
工程师00722 天前
线程同步的意义
c#·锁机制·线程同步
喵手1 个月前
线程同步:确保多线程环境中的数据一致性!
java·线程同步
獭.獭.2 个月前
Linux -- 线程互斥
linux·互斥锁·mutex·互斥量·线程互斥
egoist20232 个月前
[linux仓库]线程同步与生产者消费者模型[线程·陆]
linux·c语言·开发语言·线程同步·阻塞队列·生产者消费者模型
大佬,救命!!!2 个月前
C++多线程同步与互斥
开发语言·c++·学习笔记·多线程·互斥锁·同步与互斥·死锁和避免策略
阿巴~阿巴~2 个月前
Linux同步机制:POSIX 信号量 与 SystemV信号量 的 对比
linux·服务器·线程·信号量·线程同步·posix·system v