线程同步——C语言

1. 互斥锁

互斥锁(Mutex)是一种用于实现线程同步的低级原语,它确保在任一时刻,只有一个线程能够访问被互斥锁保护的资源(称为临界区)。互斥锁提供了一种机制,使得多个线程在访问共享资源时,能够按照某种预定的顺序或规则进行访问,避免因并发访问导致的数据不一致或竞态条件。

1.1 互斥锁

API

功能API含义
创建锁pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr)创建并初始化一个互斥锁。attr参数可以指定互斥锁属性(如类型、优先级继承等),传入NULL使用默认属性
获取锁pthread_mutex_lock(pthread_mutex_t *mutex)申请并获取互斥锁。如果互斥锁已被其他线程持有,调用线程将被阻塞,直到互斥锁变为可用
释放锁pthread_mutex_unlock(pthread_mutex_t *mutex)释放已持有的互斥锁,允许其他等待的线程获取该锁
销毁锁pthread_mutex_destroy(pthread_mutex_t *mutex)在互斥锁不再使用时,释放其占用的系统资源。必须在所有线程都未锁定该互斥锁时调用。

属性

在这里插入代码片

代码示例

#include <pthread.h>
#include <stdio.h>
pthread_mutex_t mutex;
int shared_data = 0;
void* thread_function(void* arg) {
    pthread_mutex_lock(&mutex);
    shared_data++;  // 修改共享数据
    printf("Thread %lu: Shared data = %d\n", pthread_self(), shared_data);
    pthread_mutex_unlock(&mutex);
}
 
int main() {
    pthread_t threads[2];
    pthread_mutex_init(&mutex, NULL);  // 初始化互斥锁
   
    for (int i = 0; i < 2; ++i) {
        pthread_create(&threads[i], NULL, thread_function, NULL);
    }
    for (int i = 0; i < 2; ++i) {
        pthread_join(threads[i], NULL);
    }
    pthread_mutex_destroy(&mutex);  // 销毁互斥锁
    return 0;
}

1.4 自旋锁

自旋锁(spinlock)是一种非阻塞同步机制,它通过“自旋”等待锁变得可用。当一个线程试图获取一个已被持有的自旋锁时,它将在循环中等待,而不是进入阻塞状态,消耗处理器时间直到锁变得可用。
以下的栗子和C++的实现是类似的:

#include <stdatomic.h>
#include <stdbool.h>
 
// 定义一个原子变量作为自旋锁
static atomic_bool spinlock = ATOMIC_VAR_INIT(false);
 
// 获取自旋锁的函数
void spinlock_lock(atomic_bool *lock) {
    while (atomic_exchange_explicit(lock, true, memory_order_acquire)) {
        // 当锁被占用时,循环等待
    }
}
 
// 释放自旋锁的函数
void spinlock_unlock(atomic_bool *lock) {
    atomic_store_explicit(lock, false, memory_order_release);
}
 
// 使用自旋锁的例子
void example_usage() {
    spinlock_lock(&spinlock);
    // 临界区代码
    spinlock_unlock(&spinlock);
}

2 读写锁

pthread读写锁把对共享资源的访问者分为读者和写者,读者只对共享资源进行读访问,写者只对共享资源进行写操作。在互斥机制,读者和写者都需要独立独占互斥量以独占共享资源,在读写锁机制下,允许同时有多个读者读访问共享资源,只有写者才需要独占资源。相比互斥机制,读写机制由于允许多个读者同时读访问共享资源,进一步提高了多线程的并发度。
读写锁机制
写者:写者使用写锁,如果当前没有读者,也没有其他写者,写者立即获得写锁;否则写者将等待,直到没有读者和写者。
读者:读者使用读锁,如果当前没有写者,读者立即获得读锁;否则读者等待,直到没有写者。

API

#include <pthread.h>
功能API含义
初始化int pthread_rwlock_init(pthread_rwlock_t *mutex, const pthread_mutexattr_t *attr)创建并初始化一个读写锁。attr参数可以指定互斥锁属性(如类型、优先级继承等),传入NULL使用默认属性
获取读锁int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);申请并获取读锁。
获取写锁int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);申请并获取写锁。
获取读锁非阻塞int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);非阻塞
获写锁非阻塞int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);非阻塞
释放锁int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);释放已持有的读写锁,允许其他等待的线程获取该锁
销毁锁int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);不再使用时,释放其占用的系统资源
  • 如果线程已经获取了读锁,它必须先释放读锁,才能去申请写锁。否则,尝试获取失败。
  • ‌写锁获取成功后,当前线程可以直接获取读锁‌。这是因为读写锁的设计允许读锁和写锁的分离,读锁可以被多个读线程共享,而写锁是排他的。当一个线程获得了写锁后,它可以在不释放写锁的情况下直接获取读锁,这种操作称为“锁降级”‌

代码示例

#include <pthread.h>
#include <stdio.h>
 
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
 
void* read_thread(void* arg) {
    pthread_rwlock_rdlock(&rwlock);
    printf("Read thread acquired read lock\n");
    // 执行读操作
    pthread_rwlock_unlock(&rwlock);
    return NULL;
}
 
void* write_thread(void* arg) {
    pthread_rwlock_wrlock(&rwlock);
    printf("Write thread acquired write lock\n");
    // 执行写操作
    pthread_rwlock_unlock(&rwlock);
    return NULL;
}
 
int main() {
    pthread_t thread1, thread2, thread3;
    pthread_create(&thread1, NULL, read_thread, NULL);
    pthread_create(&thread2, NULL, read_thread, NULL);
    pthread_create(&thread3, NULL, write_thread, NULL);
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);
    pthread_join(thread3, NULL);
    return 0;
}

3 条件变量

#include <pthread.h>  
#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
  
#define BUFFER_SIZE 5  
  
int buffer[BUFFER_SIZE];  
int count = 0;  
pthread_mutex_t mutex;  
pthread_cond_t not_empty;  
pthread_cond_t not_full;  
  
void* producer(void* arg) {  
    int item;  
    while (1) {  
        // 生产物品  
        item = rand() % 100; // 假设生产的是0到99之间的随机数  
          
        pthread_mutex_lock(&mutex);  
          
        // 等待缓冲区不满  
        while (count == BUFFER_SIZE) {  
            pthread_cond_wait(&not_full, &mutex);  
        }  
          
        // 将物品放入缓冲区  
        buffer[count] = item;  
        count++;  
          
        // 通知消费者  
        pthread_cond_signal(&not_empty);  
          
        pthread_mutex_unlock(&mutex);  
          
        sleep(1); // 模拟生产耗时  
    }  
    return NULL;  
}  
  
void* consumer(void* arg) {  
    while (1) {  
        pthread_mutex_lock(&mutex);  
          
        // 等待缓冲区不为空  
        while (count == 0) {  
            pthread_cond_wait(&not_empty, &mutex);  
        }  
          
        // 从缓冲区取出物品  
        int item = buffer[count - 1];  
        count--;  
          
        // 通知生产者  
        pthread_cond_signal(&not_full);  
          
        pthread_mutex_unlock(&mutex);  
          
        printf("Consumed: %d\n", item);  
          
        sleep(1); // 模拟消费耗时  
    }  
    return NULL;  
}  
  
int main() {  
    pthread_t tid_producer, tid_consumer;  
    pthread_mutex_init(&mutex, NULL);  
    pthread_cond_init(&not_empty, NULL);  
    pthread_cond_init(&not_full, NULL);  
  
    pthread_create(&tid_producer, NULL, producer, NULL);  
    pthread_create(&tid_consumer, NULL, consumer, NULL);  
  
    pthread_join(tid_producer, NULL);  
    pthread_join(tid_consumer, NULL);  
  
    pthread_mutex_destroy(&mutex);  
    pthread_cond_destroy(&not_empty);  
    pthread_cond_destroy(&not_full);  
  
    return 0;  
}

4 信号量

API

功能:  初始化信号量
返回值:失败返回-1,成功返回0
入参:  pshared:为真表示为多个进程间共享,为0表示是当前进程的局部信号量
	   value:  sem的初始值,信号量的大小
int sem_init(sem_t *psem, int pshared, unsigned int value);
功能:  获取信号量
返回值:失败返回-1,成功返回0
入参:
int sem_wait(sem_t *sem);
功能:  释放信号量
返回值:失败返回-1,成功返回0
入参:
int sem_post(sem_t *sem);
功能:  销毁信号量
返回值:失败返回-1,成功返回0
入参:
int sem_destory(sem_t *sem);

如果不销毁信号量,则线程退出时可能会产生问题,如果你的程序创建了大量的信号量而没有及时销毁,可能会将操作系统的资源耗尽。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值