[学习] C语言编程中线程安全的实现方法(示例)

C语言编程中线程安全的实现方法

在多线程编程中,线程安全(Thread Safety) 是一个非常重要的概念。当多个线程同时访问共享资源时,如果没有合理的同步机制,就可能导致数据竞争、死锁甚至程序崩溃。

本文将详细介绍在 C语言 中如何实现线程安全的几种主要方式,并提供可以实际运行的代码示例。

文章目录

  • C语言编程中线程安全的实现方法
    • 一、什么是线程安全?
    • 二、C语言中线程安全的实现方式
    • 方法一:互斥锁(Mutex)
      • ✅ 示例:用互斥锁保护计数器
    • 方法二:读写锁(Read-Write Lock)
      • ✅ 示例:读写锁保护共享结构体
    • 方法三:条件变量(Condition Variable)
      • ✅ 示例:生产者-消费者模型
    • 方法四:原子操作(Atomic Operations)
      • ✅ 示例:使用原子变量实现计数器
    • 方法五:线程局部存储(TLS)
      • ✅ 示例:使用 `__thread` 关键字(GCC扩展)
    • 总结对比表
    • 小贴士
    • 结语


一、什么是线程安全?

线程安全是指:当多个线程并发访问某个函数或对象时,其行为仍然正确,不会因为并发访问而产生错误结果。


二、C语言中线程安全的实现方式

在C语言中,最常用的线程库是 POSIX Threads(pthread)。我们主要使用以下机制来实现线程安全:

  1. 互斥锁(Mutex)
  2. 读写锁(Read-Write Lock)
  3. 条件变量(Condition Variable)
  4. 原子操作(Atomic Operations)
  5. 线程局部存储(Thread Local Storage, TLS)

下面我们逐一介绍每种方法,并给出完整示例代码。


方法一:互斥锁(Mutex)

互斥锁是最基本也是最常用的同步机制。它可以保证同一时间只有一个线程能访问共享资源。

✅ 示例:用互斥锁保护计数器

#include 
#include 
#include 

int counter = 0;
pthread_mutex_t lock; // 定义互斥锁

void* increment_counter(void* arg) {
    for (int i = 0; i < 100000; ++i) {
        pthread_mutex_lock(&lock); // 加锁
        counter++;
        pthread_mutex_unlock(&lock); // 解锁
    }
    return NULL;
}

int main() {
    pthread_t t1, t2;

    // 初始化互斥锁
    pthread_mutex_init(&lock, NULL);

    // 创建两个线程
    pthread_create(&t1, NULL, increment_counter, NULL);
    pthread_create(&t2, NULL, increment_counter, NULL);

    // 等待线程结束
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);

    // 销毁互斥锁
    pthread_mutex_destroy(&lock);

    printf("Final counter value: %d\n", counter); // 应为 200000
    return 0;
}

编译命令:

gcc -o mutex_example mutex_example.c -lpthread

方法二:读写锁(Read-Write Lock)

适用于读多写少的场景。允许多个线程同时读取资源,但只允许一个线程写入。

✅ 示例:读写锁保护共享结构体

#include 
#include 
#include 
#include 

typedef struct {
    char data[128];
} SharedData;

SharedData shared;
pthread_rwlock_t rwlock; // 定义读写锁

void* reader(void* arg) {
    for (int i = 0; i < 5; ++i) {
        pthread_rwlock_rdlock(&rwlock);
        printf("Reader: %s\n", shared.data);
        pthread_rwlock_unlock(&rwlock);
        sleep(1);
    }
    return NULL;
}

void* writer(void* arg) {
    const char* messages[] = {"Hello", "World", "Thread", "Safety", "Example"};
    for (int i = 0; i < 5; ++i) {
        pthread_rwlock_wrlock(&rwlock);
        strcpy(shared.data, messages[i]);
        printf("Writer: Updated to '%s'\n", shared.data);
        pthread_rwlock_unlock(&rwlock);
        sleep(2);
    }
    return NULL;
}

int main() {
    pthread_t r1, r2, w1;

    // 初始化读写锁
    pthread_rwlock_init(&rwlock, NULL);

    // 初始化共享数据
    strcpy(shared.data, "Initial");

    // 创建线程
    pthread_create(&r1, NULL, reader, NULL);
    pthread_create(&r2, NULL, reader, NULL);
    pthread_create(&w1, NULL, writer, NULL);

    // 等待所有线程完成
    pthread_join(r1, NULL);
    pthread_join(r2, NULL);
    pthread_join(w1, NULL);

    // 销毁锁
    pthread_rwlock_destroy(&rwlock);

    return 0;
}

编译命令:

gcc -o rwlock_example rwlock_example.c -lpthread

方法三:条件变量(Condition Variable)

用于线程间的通信,常与互斥锁配合使用,等待某个条件成立后再继续执行。

✅ 示例:生产者-消费者模型

#include 
#include 
#include 

#define MAX_ITEMS 5

int buffer = -1;
int count = 0;
pthread_mutex_t mutex;
pthread_cond_t not_empty, not_full;

void* producer(void* arg) {
    for (int i = 0; i < 10; ++i) {
        pthread_mutex_lock(&mutex);
        while (count == MAX_ITEMS) {
            pthread_cond_wait(&not_full, &mutex);
        }
        buffer = i;
        count++;
        printf("Produced: %d\n", i);
        pthread_cond_signal(&not_empty);
        pthread_mutex_unlock(&mutex);
        sleep(1);
    }
    return NULL;
}

void* consumer(void* arg) {
    for (int i = 0; i < 10; ++i) {
        pthread_mutex_lock(&mutex);
        while (count == 0) {
            pthread_cond_wait(&not_empty, &mutex);
        }
        int item = buffer;
        count--;
        printf("Consumed: %d\n", item);
        pthread_cond_signal(&not_full);
        pthread_mutex_unlock(&mutex);
        sleep(2);
    }
    return NULL;
}

int main() {
    pthread_t prod, cons;

    // 初始化锁和条件变量
    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&not_empty, NULL);
    pthread_cond_init(&not_full, NULL);

    // 创建线程
    pthread_create(&prod, NULL, producer, NULL);
    pthread_create(&cons, NULL, consumer, NULL);

    // 等待线程结束
    pthread_join(prod, NULL);
    pthread_join(cons, NULL);

    // 清理资源
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&not_empty);
    pthread_cond_destroy(&not_full);

    return 0;
}

编译命令:

gcc -o condvar_example condvar_example.c -lpthread

方法四:原子操作(Atomic Operations)

适用于对某些变量进行不可中断的操作,如递增、比较交换等。

注意:C11标准引入了 _Atomic 类型和 头文件。

✅ 示例:使用原子变量实现计数器

#include 
#include 
#include 
#include 

_Atomic int atomic_counter = 0;

void* inc_atomic(void* arg) {
    for (int i = 0; i < 100000; ++i) {
        atomic_fetch_add(&atomic_counter, 1);
    }
    return NULL;
}

int main() {
    pthread_t t1, t2;

    pthread_create(&t1, NULL, inc_atomic, NULL);
    pthread_create(&t2, NULL, inc_atomic, NULL);

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);

    printf("Atomic counter final value: %d\n", atomic_counter); // 应为 200000
    return 0;
}

编译命令:

gcc -o atomic_example atomic_example.c -lpthread -std=c11

方法五:线程局部存储(TLS)

每个线程拥有独立的变量副本,避免共享带来的竞争问题。

✅ 示例:使用 __thread 关键字(GCC扩展)

#include 
#include 
#include 

__thread int tls_counter = 0; // 每个线程独立的计数器

void* thread_func(void* arg) {
    for (int i = 0; i < 5; ++i) {
        tls_counter++;
        printf("Thread %ld: tls_counter = %d\n", pthread_self(), tls_counter);
        sleep(1);
    }
    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);

    return 0;
}

编译命令:

gcc -o tls_example tls_example.c -lpthread

总结对比表

方法 特点 使用场景
互斥锁 简单易用,粒度粗 写操作频繁
读写锁 支持并行读 读多写少
条件变量 配合互斥锁使用,支持等待通知 生产者-消费者、状态等待
原子操作 轻量级,无需锁 单个变量的简单操作
线程局部存储 线程独享变量 日志、缓存、线程私有数据

小贴士

  • 合理选择同步机制,避免过度加锁导致性能下降。
  • 使用 RAII 或宏封装锁操作,减少死锁风险。
  • 对于高性能场景,可以考虑无锁队列、CAS算法等高级技术。

结语

线程安全是编写健壮多线程程序的基础。通过合理使用互斥锁、读写锁、条件变量、原子操作和线程局部存储,你可以有效避免多线程环境下的各种并发问题。

如果你喜欢这篇文章,请点赞/收藏/转发,后续我将持续更新更多关于 C/C++ 多线程编程的内容!


研究学习不易,点赞易。
工作生活不易,收藏易,点收藏不迷茫 :)


你可能感兴趣的:(学习,c语言,安全)