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(¬_full, &mutex);
}
// 将物品放入缓冲区
buffer[count] = item;
count++;
// 通知消费者
pthread_cond_signal(¬_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(¬_empty, &mutex);
}
// 从缓冲区取出物品
int item = buffer[count - 1];
count--;
// 通知生产者
pthread_cond_signal(¬_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(¬_empty, NULL);
pthread_cond_init(¬_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(¬_empty);
pthread_cond_destroy(¬_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);
如果不销毁信号量,则线程退出时可能会产生问题,如果你的程序创建了大量的信号量而没有及时销毁,可能会将操作系统的资源耗尽。