Linux应用:线程进阶
线程同步之信号量
信号量(Semaphore)是一个整型的计数器,用于控制对共享资源的访问。它通过 PV 操作来实现同步,P 操作将信号量的值减 1,如果值小于 0 则线程阻塞;V 操作将信号量的值加 1,如果有线程在等待则唤醒一个等待的线程。
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
sem_t sem; // 定义信号量
void* thread_function(void* arg) {
// P操作
sem_wait(&sem);
printf("子线程进入临界区\n");
// 模拟临界区操作
sleep(2);
printf("子线程离开临界区\n");
// V操作
sem_post(&sem);
return NULL;
}
int main() {
pthread_t thread;
// 初始化信号量,初始值设为1
sem_init(&sem, 0, 1);
// 创建线程
if (pthread_create(&thread, NULL, thread_function, NULL) != 0) {
perror("线程创建失败");
return 1;
}
// 主线程等待一段时间
sleep(1);
// P操作 作用是将信号量的值减 1 值大于0才能减
sem_wait(&sem);
printf("主线程进入临界区\n");
// 模拟临界区操作
sleep(2);
printf("主线程离开临界区\n");
// V操作 使信号量的值加 1
sem_post(&sem);
// 等待线程结束
if (pthread_join(thread, NULL) != 0) {
perror("线程等待失败");
return 1;
}
// 销毁信号量
sem_destroy(&sem);
return 0;
}
sem_t属于信号量类型,sem是所定义的信号量变量。
sem_wait(&sem):这是信号量的 P 操作,其作用是将信号量的值减 1。若信号量的值为 0,线程会被阻塞,直至信号量的值大于 0。
printf(“线程进入临界区\n”);:输出线程进入临界区的信息。
sleep(2);:模拟在临界区内进行的操作,这里让线程休眠 2 秒。
printf(“线程离开临界区\n”);:输出线程离开临界区的信息。
sem_post(&sem):这是信号量的 V 操作,会使信号量的值加 1,同时唤醒可能处于阻塞状态的线程。
pthread_t thread;:定义一个线程标识符。
sem_init(&sem, 0, 1);:对信号量进行初始化,第二个参数为 0 表示该信号量仅在线程间共享,第三个参数 1 为信号量的初始值。
pthread_create(&thread, NULL, thread_function, NULL):创建一个新线程,新线程会执行thread_function函数。
sleep(1);:主线程等待 1 秒,确保新线程有机会运行。
sem_wait(&sem);:主线程执行 P 操作,尝试进入临界区。
printf(“主线程进入临界区\n”);:输出主线程进入临界区的信息。
sleep(2);:模拟主线程在临界区内的操作。
printf(“主线程离开临界区\n”);:输出主线程离开临界区的信息。
sem_post(&sem);:主线程执行 V 操作,离开临界区。
pthread_join(thread, NULL);:主线程等待新线程结束。
sem_destroy(&sem);:销毁信号量,释放相关资源。
创建了一个新线程,利用信号量实现了主线程和新线程对临界区的互斥访问。信号量的初始值为 1,同一时刻仅允许一个线程进入临界区。通过 P 操作和 V 操作保证了线程安全,防止多个线程同时访问临界区从而引发数据竞争问题。
线程同步之互斥锁
互斥锁(Mutex)是一种特殊的二元信号量(初始值为 1),用于保证在同一时刻只有一个线程能够访问共享资源,也就是一次只有一个线程能够获得锁,其他线程必须等待锁被释放。
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 定义并初始化互斥锁
void* thread_function(void* arg) {
// 加锁
pthread_mutex_lock(&mutex);
printf("线程进入临界区\n");
// 模拟临界区操作
sleep(2);
printf("线程离开临界区\n");
// 解锁
pthread_mutex_unlock(&mutex);
return NULL;
}
int main() {
pthread_t thread;
// 创建线程
if (pthread_create(&thread, NULL, thread_function, NULL) != 0) {
perror("线程创建失败");
return 1;
}
// 主线程等待一段时间
sleep(1);
// 加锁
pthread_mutex_lock(&mutex);
printf("主线程进入临界区\n");
// 模拟临界区操作
sleep(2);
printf("主线程离开临界区\n");
// 解锁
pthread_mutex_unlock(&mutex);
// 等待线程结束
if (pthread_join(thread, NULL) != 0) {
perror("线程等待失败");
return 1;
}
return 0;
}
stdio.h:标准输入输出库,提供了像 printf 这样用于输出信息到控制台的函数。
pthread.h:多线程相关的库,包含了创建线程、管理线程和使用互斥锁等操作所需的函数和数据类型。
unistd.h:提供了 sleep 函数,用于让线程暂停执行一段时间。
pthread_mutex_t 是一种数据类型,用于表示互斥锁。
mutex 是定义的互斥锁变量。
PTHREAD_MUTEX_INITIALIZER 是一个宏,用于静态初始化互斥锁,将其设置为初始状态。
pthread_mutex_lock(&mutex):调用该函数尝试获取互斥锁 mutex。如果互斥锁当前未被其他线程持有,调用线程会获取到锁并继续执行后续代码;如果互斥锁已经被其他线程持有,调用线程会被阻塞,直到锁被释放。
printf(“线程进入临界区\n”);:输出信息表明线程已经成功进入临界区。
sleep(2);:模拟线程在临界区内执行一些耗时操作,这里让线程暂停 2 秒。
printf(“线程离开临界区\n”);:输出信息表明线程即将离开临界区。
pthread_mutex_unlock(&mutex):调用该函数释放互斥锁,使得其他等待该锁的线程有机会获取锁并进入临界区。
pthread_t thread;:定义一个 pthread_t 类型的变量 thread,用于存储新创建线程的标识符。
pthread_create(&thread, NULL, thread_function, NULL):创建一个新线程,新线程将执行 thread_function 函数。如果线程创建失败,pthread_create 函数会返回非零值,此时程序会输出错误信息并终止。
sleep(1);:主线程暂停 1 秒,目的是让新创建的线程有机会先运行并尝试获取互斥锁。
pthread_mutex_lock(&mutex):主线程尝试获取互斥锁。如果此时新线程已经持有锁,主线程会被阻塞。
printf(“主线程进入临界区\n”);:输出信息表明主线程已经成功进入临界区。
sleep(2);:模拟主线程在临界区内执行一些耗时操作,暂停 2 秒。
printf(“主线程离开临界区\n”);:输出信息表明主线程即将离开临界区。
pthread_mutex_unlock(&mutex):主线程释放互斥锁。
pthread_join(thread, NULL):主线程等待新创建的线程执行完毕。如果等待过程中出现错误,pthread_join 函数会返回非零值,程序会输出错误信息并终止。
通过创建一个新线程和主线程,利用互斥锁实现了对临界区的互斥访问。互斥锁保证了同一时间只有一个线程能够进入临界区执行操作,避免了多个线程同时访问共享资源可能导致的数据不一致问题。当一个线程进入临界区时,会先获取互斥锁,操作完成后释放锁,使得其他线程可以继续竞争进入临界区。
线程同步之条件变量
条件变量(Condition Variable)用于线程间的同步,它允许一个线程等待某个条件满足后再继续执行。通常与互斥锁一起使用,线程在获取互斥锁后检查条件是否满足,如果不满足则通过条件变量等待,并释放互斥锁;当另一个线程改变了条件后,通过条件变量唤醒等待的线程,等待的线程重新获取互斥锁后继续执行。
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int flag = 0;
void* thread_function(void* arg) {
// 加锁
pthread_mutex_lock(&mutex);
while (flag == 0) {
// 等待条件变量,同时释放互斥锁
printf("线程得到锁\n");
sleep(1);
printf("线程释放锁 1 \n");
pthread_cond_wait(&cond, &mutex);
printf("线程释放锁 2 \n");
}
printf("线程被唤醒,条件满足\n");
// 解锁
pthread_mutex_unlock(&mutex);
return NULL;
}
int main() {
pthread_t thread;
// 创建线程
if (pthread_create(&thread, NULL, thread_function, NULL) != 0) {
perror("线程创建失败");
return 1;
}
// 主线程等待一段时间
sleep(2);
// 加锁
pthread_mutex_lock(&mutex);
flag = 1;
// 唤醒等待的线程
printf("唤醒等待的线程 1\n");
pthread_cond_signal(&cond);
printf("唤醒等待的线程 2\n");
// 解锁
pthread_mutex_unlock(&mutex);
printf("唤醒等待的线程 3\n");
// 等待线程结束
if (pthread_join(thread, NULL) != 0) {
perror("线程等待失败");
return 1;
}
return 0;
}
mutex:互斥锁,用于保护共享资源,PTHREAD_MUTEX_INITIALIZER 是一个宏,用于静态初始化互斥锁。
cond:条件变量,用于线程间的同步通信,PTHREAD_COND_INITIALIZER 是一个宏,用于静态初始化条件变量。
flag:共享变量,作为线程间通信的标志,初始值为 0。
pthread_mutex_lock(&mutex):线程进入临界区前加锁,确保同一时间只有一个线程可以访问共享资源。
while (flag == 0):检查共享变量 flag 的值,如果为 0,则线程需要等待条件满足。
pthread_cond_wait(&cond, &mutex):这是一个关键函数,它会做两件事:
释放当前持有的互斥锁 mutex,允许其他线程进入临界区。
阻塞当前线程,直到条件变量 cond 被其他线程发出信号(pthread_cond_signal 或 pthread_cond_broadcast)。
当线程被唤醒后,它会重新获取互斥锁 mutex,继续执行后续代码。
printf(“线程被唤醒,条件满足\n”);:当 flag 变为 1 时,线程被唤醒,输出相应信息。
pthread_mutex_unlock(&mutex):线程离开临界区,释放互斥锁。
pthread_create(&thread, NULL, thread_function, NULL):创建一个新线程,执行 thread_function 函数。
sleep(2):主线程休眠 2 秒,确保新线程有足够的时间进入等待状态。
pthread_mutex_lock(&mutex):主线程进入临界区,加锁。
flag = 1:修改共享变量 flag 的值,表示条件已经满足。
pthread_cond_signal(&cond):发送信号给等待在条件变量 cond 上的线程,唤醒其中一个线程。
pthread_mutex_unlock(&mutex):主线程离开临界区,释放互斥锁。
pthread_join(thread, NULL):主线程等待新线程执行完毕,确保程序正常结束。
通过互斥锁和条件变量实现了线程间的同步。新线程在 flag 为 0 时等待条件满足,主线程在适当的时候修改 flag 的值并发送信号唤醒等待的线程。这样可以避免线程忙等待,提高程序的效率。