目录
[二、互斥锁(Mutex Lock)](#二、互斥锁(Mutex Lock))
[2.代码示例(使用 C 语言和 pthread 库)](#2.代码示例(使用 C 语言和 pthread 库))
[2.代码示例(使用 C 语言和 pthread 库)](#2.代码示例(使用 C 语言和 pthread 库))
[四、自旋锁(Spin Lock)](#四、自旋锁(Spin Lock))
[2.代码示例(使用 C 语言)](#2.代码示例(使用 C 语言))
[五、读写锁(Read - Write Lock)](#五、读写锁(Read - Write Lock))
[2.代码示例(使用 C 语言和 pthread 库)](#2.代码示例(使用 C 语言和 pthread 库))
一、前言
在Linux系统中,多线程编程是实现高效并发的重要手段。线程同步机制是确保线程间协调工作、避免资源竞争的关键技术。互斥锁、条件变量、自旋锁以及读写锁是常见的线程同步工具,它们在不同场景下发挥着重要作用。
二、互斥锁(Mutex Lock)
1.互斥锁的原理
互斥锁用于保护共享资源,确保在任何时刻只有一个线程可以访问被保护的资源。当一个线程获取了互斥锁后,其他试图获取该锁的线程将被阻塞,直到锁被释放。这种机制通过互斥的方式来避免多个线程同时访问和修改共享数据,从而防止数据竞争和不一致性。
2.代码示例(使用 C 语言和 pthread 库)
cpp
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
// 定义互斥锁
pthread_mutex_t mutex;
// 共享变量
int shared_variable = 0;
// 线程函数
void *thread_function(void *arg) {
// 加锁
pthread_mutex_lock(&mutex);
// 对共享变量进行操作
shared_variable++;
printf("Thread ID: %lu, Shared variable value: %d\n", pthread_self(), shared_variable);
// 解锁
pthread_mutex_unlock(&mutex);
pthread_exit(NULL);
}
int main() {
pthread_t threads[5];
// 初始化互斥锁
if (pthread_mutex_init(&mutex, NULL)!= 0) {
perror("Mutex initialization failed");
return 1;
}
for (int i = 0; i < 5; ++i) {
// 创建线程
if (pthread_create(&threads[i], NULL, thread_function, NULL)!= 0) {
perror("Thread creation failed");
return 1;
}
}
for (int i = 0; i < 5; ++i) {
// 等待线程结束
if (pthread_join(threads[i], NULL)!= 0) {
perror("Thread join failed");
return 1;
}
}
// 销毁互斥锁
if (pthread_mutex_destroy(&mutex)!= 0) {
perror("Mutex destruction failed");
return 1;
}
return 0;
}
- 分析 :
- 首先,通过pthread_mutex_init函数初始化互斥锁mutex。这个函数在成功时返回 0,否则返回一个错误码。
- 在thread_function函数中,每个线程首先调用pthread_mutex_lock来获取互斥锁。如果此时锁已经被其他线程持有,调用线程会被阻塞在这里,直到锁被释放。
- 当线程成功获取锁后,它对共享变量shared_variable进行自增操作,并打印出线程 ID 和共享变量的值。然后,通过pthread_mutex_unlock释放互斥锁,使得其他线程有机会获取锁并访问共享资源。
- 在
main
函数中,创建了 5 个线程,并使用pthread_join等待每个线程结束。最后,通过pthread_mutex_destroy销毁互斥锁。
三、条件变量
1.条件变量的原理
条件变量通常与互斥锁一起使用,用于线程之间的同步。它允许一个线程等待某个条件为真,而另一个线程可以在条件满足时通知等待的线程。这样可以避免线程在不满足条件的情况下一直占用 CPU 资源进行无效的检查。
2.代码示例(使用 C 语言和 pthread 库)
cpp
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
// 定义互斥锁和条件变量
pthread_mutex_t mutex;
pthread_cond_t cond;
// 共享变量
int shared_variable = 0;
// 生产者线程函数
void *producer_thread(void *arg) {
pthread_mutex_lock(&mutex);
// 生产数据,修改共享变量
shared_variable++;
// 发送信号给等待的消费者线程
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
pthread_exit(NULL);
}
// 消费者线程函数
void *consumer_thread(void *arg) {
pthread_mutex_lock(&mutex);
// 检查条件是否满足,如果不满足则等待
while (shared_variable == 0) {
pthread_cond_wait(&cond, &mutex);
}
// 消费数据,访问共享变量
printf("Consumer: Shared variable value: %d\n", shared_variable);
pthread_mutex_unlock(&mutex);
pthread_exit(NULL);
}
int main() {
pthread_t producer, consumer;
// 初始化互斥锁和条件变量
if (pthread_mutex_init(&mutex, NULL)!= 0) {
perror("Mutex initialization failed");
return 1;
}
if (pthread_cond_init(&cond, NULL)!= 0) {
perror("Condition variable initialization failed");
return 1;
}
// 创建生产者线程
if (pthread_create(&producer, NULL, producer_thread, NULL)!= 0) {
perror("Producer thread creation failed");
return 1;
}
// 创建消费者线程
if (pthread_create(&consumer, NULL, consumer_thread, NULL)!= 0) {
perror("Consumer thread creation failed");
return 1;
}
// 等待生产者和消费者线程结束
if (pthread_join(producer, NULL)!= 0) {
perror("Producer thread join failed");
return 1;
}
if (pthread_join(consumer, NULL)!= 0) {
perror("Consumer thread join failed");
return 1;
}
// 销毁互斥锁和条件变量
if (pthread_mutex_destroy(&mutex)!= 0) {
perror("Mutex destruction failed");
return 1;
}
if (pthread_cond_destroy(&cond)!= 0) {
perror("Condition variable destruction failed");
return 1;
}
return 0;
}
- 分析 :
- 在
main
函数中,首先通过pthread_mutex_init和pthread_cond_init
分别初始化互斥锁mutex
和条件变量cond
。 - 生产者线程函数producer_thread获取互斥锁,修改共享变量shared_variable,然后通过pthread_cond_signal发送信号,表示共享变量已经被修改,可能满足消费者线程等待的条件。最后释放互斥锁。
- 消费者线程函数consumer_thread获取互斥锁后,检查共享变量是否满足条件(这里是
shared_variable
是否大于 0)。如果不满足条件,就调用pthread_cond_wait函数等待。这个函数会自动释放互斥锁,让其他线程(如生产者线程)能够获取锁并修改共享变量。当收到信号并且条件满足时,pthread_cond_wait会重新获取互斥锁,然后消费者线程继续执行,访问共享变量并打印相关信息,最后释放互斥锁。 - 在
main
函数最后,销毁互斥锁和条件变量。
- 在
四、自旋锁(Spin Lock)
1.自旋锁原理
自旋锁也是一种用于保护共享资源的同步机制。当一个线程试图获取自旋锁而锁已经被占用时,它不会像互斥锁那样阻塞自己,而是会一直 "自旋",即不断地检查锁是否已经被释放。这种方式在等待时间较短的情况下可能比互斥锁更高效,因为它避免了线程切换的开销。但是,如果等待时间过长,自旋锁会占用大量的 CPU 资源,因为线程一直在循环检查。
2.代码示例(使用 C 语言)
cpp
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <stdbool.h>
// 定义自旋锁结构体
typedef struct {
volatile bool locked;
} spinlock_t;
// 初始化自旋锁
void spinlock_init(spinlock_t *lock) {
lock->locked = false;
}
// 获取自旋锁
void spinlock_lock(spinlock_t *lock) {
while (__sync_lock_test_and_set(&lock->locked, true)) {
// 自旋等待
}
}
// 释放自旋锁
void spinlock_unlock(spinlock_t *lock) {
__sync_lock_release(&lock->locked);
}
// 共享变量
int shared_variable = 0;
spinlock_t spinlock;
// 线程函数
void *thread_function(void *arg) {
spinlock_lock(&spinlock);
shared_variable++;
printf("Thread ID: %lu, Shared variable value: %d\n", pthread_self(), shared_variable);
spinlock_unlock(&spinlock);
pthread_exit(NULL);
}
int main() {
pthread_t threads[5];
spinlock_init(&spinlock);
for (int i = 0; i < 5; ++i) {
if (pthread_create(&threads[i], NULL, thread_function, NULL)!= 0) {
perror("Thread creation failed");
return 1;
}
}
for (int i = 0; i < 5; ++i) {
if (pthread_join(threads[i], NULL)!= 0) {
perror("Thread join failed");
return 1;
}
}
return 0;
}
- 分析 :
- 首先定义了自旋锁结构体spinlock_t,其中包含一个volatile bool类型的变量locked用于表示锁的状态。volatile关键字用于告诉编译器这个变量可能会被外部因素(如其他线程)改变,不要对其进行优化。
- spinlock_init函数用于初始化自旋锁,将locked设置为false,表示锁未被占用。
spinlock_lock
函数通过__sync_lock_test_and_set
原子操作来尝试获取自旋锁。这个原子操作会检查locked
的值,如果为false
,则将其设置为true
并返回旧值(此时获取锁成功);如果为true
,则返回true
,表示锁已经被占用,线程会进入循环等待(自旋),直到获取到锁。- 在thread_function函数中,线程首先获取自旋锁,然后对共享变量shared_variable进行操作,打印相关信息,最后释放自旋锁。
- 在main函数中,创建了多个线程并等待它们结束。
五、读写锁(Read - Write Lock)
1.读写锁原理
读写锁用于区分对共享资源的读操作和写操作。它允许多个线程同时对共享资源进行读操作,但在有线程进行写操作时,其他线程(无论是读还是写)都需要等待。这种机制适用于共享资源读操作频繁而写操作相对较少的场景,可以提高并发性能。
2.代码示例(使用 C 语言和 pthread 库)
cpp
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
// 定义读写锁
pthread_rwlock_t rwlock;
// 共享变量
int shared_variable = 0;
// 读线程函数
void *read_thread(void *arg) {
// 获取读锁
pthread_rwlock_rdlock(&rwlock);
printf("Read thread ID: %lu, Shared variable value: %d\n", pthread_self(), shared_variable);
// 释放读锁
pthread_rwlock_unlock(&rwlock);
pthread_exit(NULL);
}
// 写线程函数
void *write_thread(void *arg) {
// 获取写锁
pthread_rwlock_wrlock(&rwlock);
shared_variable++;
printf("Write thread ID: %lu, Shared variable value: %d\n", pthread_self(), shared_variable);
// 释放写锁
pthread_rwlock_unlock(&rwlock);
pthread_exit(NULL);
}
int main() {
pthread_t read_threads[5];
pthread_t write_thread;
// 初始化读写锁
if (pthread_rwlock_init(&rwlock, NULL)!= 0) {
perror("Read - write lock initialization failed");
return 1;
}
for (int i = 0; i < 5; ++i) {
// 创建读线程
if (pthread_create(&read_threads[i], NULL, read_thread, NULL)!= 0) {
perror("Read thread creation failed");
return 1;
}
}
// 创建写线程
if (pthread_create(&write_thread, NULL, write_thread, NULL)!= 0) {
perror("Write thread creation failed");
return 1;
}
for (int i = 0; i < 5; ++i) {
// 等待读线程结束
if (pthread_join(read_threads[i], NULL)!= 0) {
perror("Read thread join failed");
return 1;
}
}
if (pthread_join(write_thread, NULL)!= 0) {
perror("Write thread join failed");
return 1;
}
// 销毁读写锁
if (pthread_rwlock_destroy(&rwlock)!= 0) {
perror("Read - write lock destruction failed");
return 1;
}
return 0;
}
- 分析 :
- 在
main
函数中,首先通过pthread_rwlock_init函数初始化读写锁rwlock。 - 读线程函数read_thread通过pthread_rwlock_rdlock获取读锁,这样多个读线程可以同时获取读锁并访问共享变量shared_variable。读取完成后,通过pthread_rwlock_unlock释放读锁。
- 写线程函数write_thread通过pthread_rwlock_wrlock获取写锁。当一个线程获取写锁后,其他线程(无论是读还是写)都无法获取锁,直到写操作完成。写线程对共享变量进行自增操作,打印相关信息,然后释放写锁。
- 在
main
函数最后,等待所有线程结束并销毁读写锁。
- 在
六、总结
互斥锁、条件变量、自旋锁和读写锁在Linux线程同步中各有特点和适用场景。互斥锁是最常用的同步工具,适用于大多数场景。条件变量用于在特定条件满足时通知线程。自旋锁适用于对锁的等待时间较短且不希望线程上下文切换的场景。读写锁则在对共享资源进行读和写操作时提高效率。