并发编程与多线程
并发编程在 C 语言中的应用非常广泛,特别是在高性能计算、服务器开发和嵌入式系统中。并发编程是一种设计思想,而多线程则是其常见的实现方式。虽然 C11 标准引入了对多线程的支持,但在很多应用中,POSIX 线程(pthread)仍占据主导地位,尤其是在跨平台开发和操作系统底层编程中。POSIX 线程提供了一套标准化的多线程编程接口,涵盖了线程创建、同步机制(如互斥锁、条件变量等)以及线程管理等功能,这使其成为实现高效并发应用的首选工具。
接下来,我们将深入探讨如何使用 POSIX 线程进行多线程编程。
1. POSIX线程(pthread)
1.1 线程的创建、终止与同步
在C语言中,使用pthread
库进行多线程编程时,常用的操作包括创建线程、终止线程和等待线程结束。
1.1.1 线程的创建与终止
线程创建是通过pthread_create()
函数来实现的,该函数原型如下:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine)(void *), void *arg);
- thread:返回创建的线程ID。
- attr:线程的属性,通常传入
NULL
表示使用默认属性。 - start_routine:线程执行的函数。
- arg:传递给线程函数的参数。
线程的终止使用pthread_exit()
或返回的方式,一旦线程执行完start_routine
函数的代码,线程会自动退出。
创建线程的简单示例:
#include <pthread.h>
#include <stdio.h>
void* print_message(void* msg) {
printf("%s\n", (char*)msg);
return NULL;
}
int main() {
pthread_t thread;
char *message = "Hello, World!";
pthread_create(&thread, NULL, print_message, (void*)message);
pthread_join(thread, NULL); // 等待线程结束
return 0;
}
1.1.2 线程的同步
线程同步是为了避免多个线程并发访问共享资源时发生数据竞争,常用的同步机制有互斥锁、条件变量和信号量。
1.2 线程间通信:条件变量与信号量
1.2.1 条件变量
条件变量用于线程之间的同步,允许一个线程在某个条件成立时通知另一个线程继续执行。
pthread_cond_wait()
函数用于阻塞线程,直到收到条件变量的通知。其原型如下:
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
- cond:条件变量。
- mutex:互斥锁,必须先加锁。
条件变量的使用示例:
#include <pthread.h>
#include <stdio.h>
pthread_mutex_t mutex;
pthread_cond_t cond;
void* wait_for_signal(void* arg) {
pthread_mutex_lock(&mutex);
printf("Waiting for condition...\n");
pthread_cond_wait(&cond, &mutex);
printf("Condition met, thread resuming.\n");
pthread_mutex_unlock(&mutex);
return NULL;
}
void* signal_condition(void* arg) {
pthread_mutex_lock(&mutex);
printf("Signaling condition...\n");
pthread_cond_signal(&cond); // 通知等待线程
pthread_mutex_unlock(&mutex);
return NULL;
}
int main() {
pthread_t thread1, thread2;
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&cond, NULL);
pthread_create(&thread1, NULL, wait_for_signal, NULL);
pthread_create(&thread2, NULL, signal_condition, NULL);
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}
注意事项:
pthread_cond_wait()
必须在互斥锁保护下调用,因为它会释放锁并进入阻塞状态,避免其他线程在未同步的情况下修改共享数据。- 条件变量唤醒时,最好使用
pthread_cond_wait
的循环,因为可能存在虚假唤醒(即不等条件满足时,线程也会被唤醒)。
1.2.2 信号量
信号量用于控制对资源的访问,尤其是在并发量较高时进行资源的计数管理。POSIX信号量在semaphore.h
中定义,使用sem_wait()
和sem_post()
等函数来进行操作。
信号量的简单使用示例:
#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>
sem_t sem;
void* thread_func(void* arg) {
sem_wait(&sem); // 信号量减1
printf("Thread is running.\n");
sem_post(&sem); // 信号量加1
return NULL;
}
int main() {
pthread_t threads[5];
sem_init(&sem, 0, 3); // 信号量初始值为3
for (int i = 0; i < 5; ++i) {
pthread_create(&threads[i], NULL, thread_func, NULL);
}
for (int i = 0; i < 5; ++i) {
pthread_join(threads[i], NULL);
}
sem_destroy(&sem);
return 0;
}
在这个例子中,信号量用于限制同时运行的线程数量为3。
2. 线程同步与互斥
2.1 互斥锁
互斥锁用于保护共享资源,防止多个线程同时访问时发生数据竞争。
互斥锁的创建、使用和销毁:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&mutex);
// 执行临界区代码
pthread_mutex_unlock(&mutex);
2.2 读写锁
读写锁(pthread_rwlock_t
)允许多个读线程并发执行,但写线程访问时是独占的。
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
// 读操作
pthread_rwlock_rdlock(&rwlock);
// 执行读操作
pthread_rwlock_unlock(&rwlock);