linux的并发和竞争处理(最全)

本文详细介绍了在Linux系统中处理并发和竞争问题的策略,包括互斥锁的使用、信号量的同步机制、条件变量的通信、读写锁的应用以及原子操作的重要性,同时提到了工具如Valgrind和ThreadSanitizer的辅助作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在 Linux 系统中,处理并发和竞争的问题是非常重要的,特别是在多线程编程和多进程编程中。以下是处理并发和竞争的一些常见策略和技术:

互斥锁(Mutex)

互斥锁是最常见的并发控制机制之一。它可以确保在任何时候只有一个线程能够访问被保护的资源,从而防止竞争条件。在 Linux 中,可以使用 pthread_mutex_lock() 和 pthread_mutex_unlock() 函数来实现互斥锁。

互斥锁实现并发竞争处理的步骤

1、初始化互斥锁:在需要保护的临界区资源周围,首先要创建和初始化一个互斥锁。

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

2、加锁(Lock):在访问临界区资源之前,需要先加锁,以防止其他线程同时访问。

pthread_mutex_lock(&mutex); // 加锁

3、访问临界区资源:在临界区内对共享资源进行操作。

4、解锁(Unlock):在临界区操作完成后,需要释放锁,以允许其他线程访问临界区资源。

pthread_mutex_unlock(&mutex); // 解锁

5、销毁互斥锁:在程序结束时,应该销毁不再需要的互斥锁。

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

互斥锁实现并发竞争处理的代码

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

#define NUM_THREADS 5

int shared_resource = 0;
pthread_mutex_t mutex;

void *thread_func(void *arg) {
    int thread_id = *(int *)arg;
    
    pthread_mutex_lock(&mutex); // 加锁
    
    // 临界区操作:对共享资源进行累加
    shared_resource += thread_id;
    printf("Thread %d: shared_resource = %d\n", thread_id, shared_resource);
    
    pthread_mutex_unlock(&mutex); // 解锁
    
    pthread_exit(NULL);
}

int main() {
    pthread_t threads[NUM_THREADS];
    int thread_args[NUM_THREADS];
    int i;

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

    // 创建多个线程
    for (i = 0; i < NUM_THREADS; i++) {
        thread_args[i] = i;
        pthread_create(&threads[i], NULL, thread_func, (void *)&thread_args[i]);
    }

    // 等待所有线程完成
    for (i = 0; i < NUM_THREADS; i++) {
        pthread_join(threads[i], NULL);
    }

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

    return 0;
}

信号量(Semaphore)

信号量是一种更加通用的同步原语,它允许多个线程同时访问共享资源,但可以限制同时访问资源的数量。在 Linux 中,可以使用 sem_init()、sem_wait() 和 sem_post() 函数来操作信号量。在 POSIX 线程库中,通常使用信号量来实现线程之间的同步。

信号量处理并发和竞争的一般步骤

创建信号量:首先需要创建一个信号量,并指定其初始值。可以使用 sem_init 函数来完成这个步骤。

对信号量进行操作:在需要访问共享资源之前,线程会尝试对信号量进行操作,以确定是否可以访问资源。可以使用 sem_wait 函数来尝试获取信号量,如果信号量的值大于 0,则表示可以访问资源;否则线程会阻塞等待。使用 sem_post 函数来释放信号量,表示资源已经被释放。

访问共享资源:一旦获得了信号量,线程就可以安全地访问共享资源。

释放信号量:线程在完成对共享资源的访问后,应该及时释放信号量,以便其他线程可以继续访问资源。

#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>
#include <unistd.h>

#define NUM_THREADS 5

// 共享资源
int shared_resource = 0;

// 信号量
sem_t semaphore;

void *thread_func(void *arg) {
    int tid = *((int *)arg);

    // 尝试获取信号量
    sem_wait(&semaphore);

    // 访问共享资源
    printf("Thread %d: Accessing shared resource\n", tid);
    shared_resource++;
    printf("Thread %d: Updated shared resource: %d\n", tid, shared_resource);
    sleep(1);

    // 释放信号量
    sem_post(&semaphore);

    pthread_exit(NULL);
}

int main() {
    pthread_t threads[NUM_THREADS];
    int thread_ids[NUM_THREADS];

    // 初始化信号量
    sem_init(&semaphore, 0, 1); // 初始值为 1

    // 创建线程
    for (int i = 0; i < NUM_THREADS; ++i) {
        thread_ids[i] = i;
        pthread_create(&threads[i], NULL, thread_func, &thread_ids[i]);
    }

    // 等待线程退出
    for (int i = 0; i < NUM_THREADS; ++i) {
        pthread_join(threads[i], NULL);
    }

    // 销毁信号量
    sem_destroy(&semaphore);

    return 0;
}

条件变量(Condition Variable)

条件变量允许线程在特定条件下等待或者被唤醒。它通常与互斥锁一起使用,用于线程之间的通信和同步。在 Linux 中,可以使用 pthread_cond_wait()、pthread_cond_signal() 和 pthread_cond_broadcast() 函数来操作条件变量。

条件变量处理并发和竞争条件的一般步骤

创建条件变量和互斥锁:首先需要创建一个条件变量和一个互斥锁,用于保护共享资源。可以使用 pthread_cond_init 函数和 pthread_mutex_init 函数来完成这个步骤。

对共享资源进行访问:线程在访问共享资源之前,需要先获取互斥锁,以确保同一时刻只有一个线程能够访问资源。

检查条件:线程在访问共享资源之前,可能需要检查某些条件是否满足。如果条件不满足,线程可以调用 pthread_cond_wait 函数来等待条件变量的发生。

等待条件变量的发生:如果条件不满足,线程会阻塞在条件变量上等待,直到其他线程通过 pthread_cond_signal 或 pthread_cond_broadcast 函数通知条件变量的发生。

释放互斥锁:一旦条件满足,线程就可以安全地访问共享资源。在访问完毕后,线程应该释放互斥锁,以便其他线程可以访问资源。

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

#define NUM_THREADS 5

// 共享资源
int shared_resource = 0;

// 条件变量和互斥锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

void *thread_func(void *arg) {
    int tid = *((int *)arg);

    // 获取互斥锁
    pthread_mutex_lock(&mutex);

    // 检查条件
    while (shared_resource < tid) {
        // 等待条件变量的发生
        pthread_cond_wait(&cond, &mutex);
    }

    // 访问共享资源
    printf("Thread %d: Accessing shared resource\n", tid);
    shared_resource++;
    printf("Thread %d: Updated shared resource: %d\n", tid, shared_resource);
    sleep(1);

    // 释放互斥锁
    pthread_mutex_unlock(&mutex);

    pthread_exit(NULL);
}

int main() {
    pthread_t threads[NUM_THREADS];
    int thread_ids[NUM_THREADS];

    // 创建线程
    for (int i = 0; i < NUM_THREADS; ++i) {
        thread_ids[i] = i;
        pthread_create(&threads[i], NULL, thread_func, &thread_ids[i]);
    }

    // 发出条件变量的通知
    pthread_mutex_lock(&mutex);
    pthread_cond_broadcast(&cond);
    pthread_mutex_unlock(&mutex);

    // 等待线程退出
    for (int i = 0; i < NUM_THREADS; ++i) {
        pthread_join(threads[i], NULL);
    }

    return 0;
}

读写锁(Read-Write Lock)

读写锁允许多个线程同时读取共享资源,但只允许一个线程写入共享资源。这在某些场景下可以提高性能。在 Linux 中,可以使用 pthread_rwlock_rdlock()、pthread_rwlock_wrlock() 和 pthread_rwlock_unlock() 函数来操作读写锁。读写锁适用于读操作频繁而写操作较少的场景,可以提高并发性能。

读写锁处理并发和竞争条件的一般步骤:

创建读写锁:首先需要创建一个读写锁,可以使用 pthread_rwlock_init 函数来完成这个步骤。

读取共享资源:对于读取共享资源的线程,首先需要获取读取锁(读锁),以允许多个线程同时读取共享资源。

写入共享资源:对于写入共享资源的线程,需要获取独占锁(写锁),以确保同一时刻只有一个线程能够写入共享资源。

释放锁:在读取或写入完成后,线程需要释放相应的锁,以便其他线程可以继续访问共享资源。

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

// 定义读写锁并初始化为静态初始值
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
int shared_resource = 0; // 共享资源

// 读取线程函数
void *reader(void *arg) {
    // 获取读锁
    pthread_rwlock_rdlock(&rwlock);
    // 读取共享资源
    printf("Reader: read shared resource: %d\n", shared_resource);
    // 释放读锁
    pthread_rwlock_unlock(&rwlock);
    return NULL;
}

// 写入线程函数
void *writer(void *arg) {
    // 获取写锁
    pthread_rwlock_wrlock(&rwlock);
    // 更新共享资源
    shared_resource++;
    printf("Writer: update shared resource: %d\n", shared_resource);
    // 释放写锁
    pthread_rwlock_unlock(&rwlock);
    return NULL;
}

int main() {
    pthread_t readers[3], writers[2];

    // 创建读取线程
    for (int i = 0; i < 3; ++i) {
        pthread_create(&readers[i], NULL, reader, NULL);
    }

    // 创建写入线程
    for (int i = 0; i < 2; ++i) {
        pthread_create(&writers[i], NULL, writer, NULL);
    }

    // 等待读取线程结束
    for (int i = 0; i < 3; ++i) {
        pthread_join(readers[i], NULL);
    }

    // 等待写入线程结束
    for (int i = 0; i < 2; ++i) {
        pthread_join(writers[i], NULL);
    }

    return 0;
}

原子操作

原子操作是不可中断的操作,它们可以确保在多线程环境中对共享数据的访问是原子的,即不会被其他线程打断。在 C/C++ 中,可以使用原子操作来避免竞争条件和数据竞争的问题。

#include <iostream>
#include <thread>
#include <atomic>

std::atomic<int> shared_resource(0);

void reader() {
    int value = shared_resource.load(); // 读取共享资源的值
    std::cout << "Reader: read shared resource: " << value << std::endl;
}

void writer() {
    shared_resource.fetch_add(1); // 更新共享资源的值
    std::cout << "Writer: update shared resource: " << shared_resource << std::endl;
}

int main() {
    std::thread readers[3], writers[2];

    // 创建读取线程
    for (int i = 0; i < 3; ++i) {
        readers[i] = std::thread(reader);
    }

    // 创建写入线程
    for (int i = 0; i < 2; ++i) {
        writers[i] = std::thread(writer);
    }

    // 等待读取线程结束
    for (int i = 0; i < 3; ++i) {
        readers[i].join();
    }

    // 等待写入线程结束
    for (int i = 0; i < 2; ++i) {
        writers[i].join();
    }

    return 0;
}

除了上述的一些锁,还有一些其他的方法处理竞争和并发。比如,在代码中使用锁和同步原语时,需要小心避免出现竞争条件。可以使用工具如 Valgrind、ThreadSanitizer 等来检测和调试并发问题。尽量避免使用全局变量或者共享数据结构,尽可能将数据局部化,减少竞争条件的发生。根据具体的场景和需求选择合适的并发控制策略,例如读多写少的场景适合使用读写锁,而写多的场景适合使用互斥锁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

稚肩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值