C语言多线程编程全面精讲:从小白到专家(附示例源码)
一、多线程编程基础概念
1.1 线程与进程的区别
在开始多线程编程前,我们需要明确线程(Thread)和进程(Process)的关键区别:
- 进程:操作系统资源分配的基本单位,拥有独立的地址空间
- 线程:CPU调度的基本单位,共享进程的地址空间
/*
* 进程 vs 线程对比:
*
* | 特性 | 进程 | 线程 |
* |---------------|------------------------|------------------------|
* | 内存空间 | 独立 | 共享 |
* | 创建开销 | 大 | 小 |
* | 通信方式 | 管道、消息队列、共享内存 | 全局变量、互斥锁等 |
* | 上下文切换 | 慢 | 快 |
* | 安全性 | 高(隔离) | 低(共享内存易冲突) |
*/
1.2 POSIX线程标准
C语言本身不包含多线程支持,我们使用POSIX线程(Pthreads)标准:
#include <pthread.h> // 必须包含的头文件
// 编译时需要链接pthread库
// gcc program.c -o program -lpthread
二、线程创建与管理
2.1 创建线程:pthread_create()
函数原型:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
参数解析:
thread
:指向线程标识符的指针attr
:设置线程属性,NULL表示默认start_routine
:线程函数入口地址arg
:传递给线程函数的参数
基础示例:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
// 线程函数
void* print_message(void *msg) {
char *message = (char *)msg;
for (int i = 0; i < 5; i++) {
printf("%s: %d\n", message, i);
sleep(1); // 模拟耗时操作
}
return NULL;
}
int main() {
pthread_t thread1, thread2;
char *msg1 = "Thread 1";
char *msg2 = "Thread 2";
// 创建两个线程
pthread_create(&thread1, NULL, print_message, (void *)msg1);
pthread_create(&thread2, NULL, print_message, (void *)msg2);
// 等待线程结束
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
printf("All threads completed!\n");
return 0;
}
2.2 线程终止
线程终止的三种方式:
- 从线程函数return
- 调用pthread_exit()
- 被其他线程取消(pthread_cancel)
#include <stdio.h>
#include <pthread.h>
void* thread_func(void *arg) {
int id = *(int *)arg;
if (id == 1) {
// 方式1:通过return退出
printf("Thread %d exiting by return\n", id);
return (void *)1;
} else {
// 方式2:通过pthread_exit退出
printf("Thread %d exiting by pthread_exit\n", id);
pthread_exit((void *)2);
}
}
int main() {
pthread_t t1, t2;
int id1 = 1, id2 = 2;
pthread_create(&t1, NULL, thread_func, &id1);
pthread_create(&t2, NULL, thread_func, &id2);
void *retval;
pthread_join(t1, &retval);
printf("Thread 1 returned %ld\n", (long)retval);
pthread_join(t2, &retval);
printf("Thread 2 returned %ld\n", (long)retval);
return 0;
}
三、线程同步机制
3.1 互斥锁(Mutex)
基本用法:
#include <stdio.h>
#include <pthread.h>
int shared_counter = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void* increment_counter(void *arg) {
for (int i = 0; i < 100000; i++) {
pthread_mutex_lock(&mutex); // 加锁
shared_counter++;
pthread_mutex_unlock(&mutex); // 解锁
}
return NULL;
}
int main() {
pthread_t t1, t2;
pthread_create(&t1, NULL, increment_counter, NULL);
pthread_create(&t2, NULL, increment_counter, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
printf("Final counter value: %d (expected 200000)\n", shared_counter);
return 0;
}
高级特性 - 尝试加锁:
// 在原有代码中修改increment_counter函数
void* increment_counter(void *arg) {
for (int i = 0; i < 100000; ) {
if (pthread_mutex_trylock(&mutex) == 0) { // 尝试加锁
shared_counter++;
pthread_mutex_unlock(&mutex);
i++;
} else {
// 锁被占用时的处理
usleep(100); // 短暂休眠避免忙等待
}
}
return NULL;
}
3.2 条件变量(Condition Variables)
条件变量用于线程间的通知机制:
#include <stdio.h>
#include <pthread.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int ready = 0;
void* producer(void *arg) {
printf("Producer starting...\n");
sleep(2); // 模拟生产耗时
pthread_mutex_lock(&mutex);
ready = 1;
printf("Producer: data is ready!\n");
pthread_cond_signal(&cond); // 通知消费者
pthread_mutex_unlock(&mutex);
return NULL;
}
void* consumer(void *arg) {
printf("Consumer waiting...\n");
pthread_mutex_lock(&mutex);
while (!ready) { // 必须用while循环检查条件
pthread_cond_wait(&cond, &mutex); // 自动释放锁并等待
}
printf("Consumer: processing data\n");
pthread_mutex_unlock(&mutex);
return NULL;
}
int main() {
pthread_t prod, cons;
pthread_create(&prod, NULL, producer, NULL);
pthread_create(&cons, NULL, consumer, NULL);
pthread_join(prod, NULL);
pthread_join(cons, NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}
3.3 读写锁(Read-Write Locks)
适用于读多写少的场景:
#include <stdio.h>
#include <pthread.h>
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
int shared_data = 0;
void* reader(void *arg) {
int id = *(int *)arg;
pthread_rwlock_rdlock(&rwlock);
printf("Reader %d: read shared_data = %d\n", id, shared_data);
usleep(100000); // 模拟读取耗时
pthread_rwlock_unlock(&rwlock);
return NULL;
}
void* writer(void *arg) {
int id = *(int *)arg;
pthread_rwlock_wrlock(&rwlock);
shared_data++;
printf("Writer %d: updated shared_data to %d\n", id, shared_data);
usleep(200000); // 模拟写入耗时
pthread_rwlock_unlock(&rwlock);
return NULL;
}
int main() {
pthread_t readers[5], writers[2];
int ids[5] = {1, 2, 3, 4, 5};
// 创建3个读者
for (int i = 0; i < 3; i++) {
pthread_create(&readers[i], NULL, reader, &ids[i]);
}
// 创建2个写者
for (int i = 0; i < 2; i++) {
pthread_create(&writers[i], NULL, writer, &ids[i]);
}
// 再创建2个读者
for (int i = 3; i < 5; i++) {
pthread_create(&readers[i], NULL, reader, &ids[i]);
}
// 等待所有线程完成
for (int i = 0; i < 5; i++) {
pthread_join(readers[i], NULL);
}
for (int i = 0; i < 2; i++) {
pthread_join(writers[i], NULL);
}
pthread_rwlock_destroy(&rwlock);
return 0;
}
四、线程属性与高级控制
4.1 设置线程属性
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void* thread_func(void *arg) {
printf("This is a detached thread!\n");
sleep(2);
printf("Detached thread exiting\n");
return NULL;
}
int main() {
pthread_attr_t attr;
pthread_t thread;
// 初始化线程属性
pthread_attr_init(&attr);
// 设置为分离状态(不需要pthread_join)
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
// 设置栈大小(1MB)
size_t stack_size = 1024 * 1024;
pthread_attr_setstacksize(&attr, stack_size);
pthread_create(&thread, &attr, thread_func, NULL);
// 销毁属性对象
pthread_attr_destroy(&attr);
// 主线程等待一段时间让分离线程完成
sleep(3);
printf("Main thread exiting\n");
return 0;
}
4.2 线程局部存储(Thread-Local Storage)
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
// 定义线程局部变量
__thread int thread_local_var = 0;
void* thread_func(void *arg) {
int id = *(int *)arg;
thread_local_var = id * 10;
printf("Thread %d: initial value = %d\n", id, thread_local_var);
for (int i = 0; i < 3; i++) {
thread_local_var++;
printf("Thread %d: value = %d\n", id, thread_local_var);
sleep(1);
}
return NULL;
}
int main() {
pthread_t t1, t2;
int id1 = 1, id2 = 2;
pthread_create(&t1, NULL, thread_func, &id1);
pthread_create(&t2, NULL, thread_func, &id2);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
return 0;
}
五、线程安全与死锁预防
5.1 线程安全函数设计
非线程安全示例:
#include <stdio.h>
#include <pthread.h>
// 非线程安全的伪随机数生成器
unsigned int next = 1;
/* 非线程安全的rand实现 */
int my_rand(void) {
next = next * 1103515245 + 12345;
return (unsigned int)(next / 65536) % 32768;
}
void* thread_func(void *arg) {
for (int i = 0; i < 5; i++) {
printf("Thread %ld: %d\n", (long)arg, my_rand());
}
return NULL;
}
int main() {
pthread_t t1, t2;
pthread_create(&t1, NULL, thread_func, (void *)1);
pthread_create(&t2, NULL, thread_func, (void *)2);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
return 0;
}
线程安全改进版:
#include <stdio.h>
#include <pthread.h>
// 线程安全的伪随机数生成器
unsigned int next = 1;
pthread_mutex_t rand_mutex = PTHREAD_MUTEX_INITIALIZER;
/* 线程安全的rand实现 */
int my_rand_safe(void) {
pthread_mutex_lock(&rand_mutex);
next = next * 1103515245 + 12345;
int result = (unsigned int)(next / 65536) % 32768;
pthread_mutex_unlock(&rand_mutex);
return result;
}
void* thread_func(void *arg) {
for (int i = 0; i < 5; i++) {
printf("Thread %ld: %d\n", (long)arg, my_rand_safe());
}
return NULL;
}
int main() {
pthread_t t1, t2;
pthread_create(&t1, NULL, thread_func, (void *)1);
pthread_create(&t2, NULL, thread_func, (void *)2);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_mutex_destroy(&rand_mutex);
return 0;
}
5.2 死锁示例与预防
死锁示例:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
void* thread1_func(void *arg) {
pthread_mutex_lock(&mutex1);
printf("Thread 1 acquired mutex1\n");
sleep(1); // 故意sleep让死锁更容易出现
pthread_mutex_lock(&mutex2); // 这里会阻塞,因为thread2持有mutex2
printf("Thread 1 acquired mutex2\n");
// 临界区代码...
pthread_mutex_unlock(&mutex2);
pthread_mutex_unlock(&mutex1);
return NULL;
}
void* thread2_func(void *arg) {
pthread_mutex_lock(&mutex2);
printf("Thread 2 acquired mutex2\n");
sleep(1); // 故意sleep让死锁更容易出现
pthread_mutex_lock(&mutex1); // 这里会阻塞,因为thread1持有mutex1
printf("Thread 2 acquired mutex1\n");
// 临界区代码...
pthread_mutex_unlock(&mutex1);
pthread_mutex_unlock(&mutex2);
return NULL;
}
int main() {
pthread_t t1, t2;
pthread_create(&t1, NULL, thread1_func, NULL);
pthread_create(&t2, NULL, thread2_func, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
printf("This line will never be reached due to deadlock!\n");
return 0;
}
死锁预防方案:
- 固定加锁顺序:
// 所有线程都按照mutex1->mutex2的顺序加锁
void* thread_safe_func(void *arg) {
pthread_mutex_lock(&mutex1);
pthread_mutex_lock(&mutex2);
// 临界区代码...
pthread_mutex_unlock(&mutex2);
pthread_mutex_unlock(&mutex1);
return NULL;
}
- 使用pthread_mutex_trylock:
void* thread_trylock_func(void *arg) {
while (1) {
if (pthread_mutex_trylock(&mutex1) == 0) {
if (pthread_mutex_trylock(&mutex2) == 0) {
// 成功获取两个锁
printf("Thread acquired both locks\n");
// 临界区代码...
pthread_mutex_unlock(&mutex2);
pthread_mutex_unlock(&mutex1);
break;
}
// 获取mutex2失败,释放mutex1避免死锁
pthread_mutex_unlock(&mutex1);
}
usleep(100000); // 避免忙等待
}
return NULL;
}
六、线程池实现
一个简单的线程池实现:
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#define THREAD_POOL_SIZE 4
#define TASK_QUEUE_SIZE 256
typedef struct {
void (*function)(void *);
void *arg;
} Task;
typedef struct {
Task task_queue[TASK_QUEUE_SIZE];
int queue_front;
int queue_rear;
int queue_count;
pthread_mutex_t lock;
pthread_cond_t notify;
pthread_t workers[THREAD_POOL_SIZE];
int shutdown;
} ThreadPool;
void* worker_thread(void *arg) {
ThreadPool *pool = (ThreadPool *)arg;
while (1) {
pthread_mutex_lock(&pool->lock);
// 等待任务或关闭信号
while (pool->queue_count == 0 && !pool->shutdown) {
pthread_cond_wait(&pool->notify, &pool->lock);
}
// 收到关闭信号且无任务可处理时退出
if (pool->shutdown && pool->queue_count == 0) {
pthread_mutex_unlock(&pool->lock);
pthread_exit(NULL);
}
// 取出任务
Task task = pool->task_queue[pool->queue_front];
pool->queue_front = (pool->queue_front + 1) % TASK_QUEUE_SIZE;
pool->queue_count--;
pthread_mutex_unlock(&pool->lock);
// 执行任务
(task.function)(task.arg);
}
return NULL;
}
ThreadPool* thread_pool_create() {
ThreadPool *pool = malloc(sizeof(ThreadPool));
if (!pool) return NULL;
pool->queue_front = 0;
pool->queue_rear = 0;
pool->queue_count = 0;
pool->shutdown = 0;
pthread_mutex_init(&pool->lock, NULL);
pthread_cond_init(&pool->notify, NULL);
for (int i = 0; i < THREAD_POOL_SIZE; i++) {
pthread_create(&pool->workers[i], NULL, worker_thread, pool);
}
return pool;
}
int thread_pool_add_task(ThreadPool *pool, void (*function)(void *), void *arg) {
pthread_mutex_lock(&pool->lock);
if (pool->queue_count == TASK_QUEUE_SIZE) {
pthread_mutex_unlock(&pool->lock);
return -1; // 队列已满
}
if (pool->shutdown) {
pthread_mutex_unlock(&pool->lock);
return -2; // 线程池已关闭
}
// 添加任务到队列
pool->task_queue[pool->queue_rear].function = function;
pool->task_queue[pool->queue_rear].arg = arg;
pool->queue_rear = (pool->queue_rear + 1) % TASK_QUEUE_SIZE;
pool->queue_count++;
// 通知一个等待的线程
pthread_cond_signal(&pool->notify);
pthread_mutex_unlock(&pool->lock);
return 0;
}
void thread_pool_destroy(ThreadPool *pool) {
if (!pool) return;
pthread_mutex_lock(&pool->lock);
pool->shutdown = 1;
pthread_mutex_unlock(&pool->lock);
// 唤醒所有线程
pthread_cond_broadcast(&pool->notify);
// 等待所有线程退出
for (int i = 0; i < THREAD_POOL_SIZE; i++) {
pthread_join(pool->workers[i], NULL);
}
pthread_mutex_destroy(&pool->lock);
pthread_cond_destroy(&pool->notify);
free(pool);
}
// 测试任务函数
void print_task(void *arg) {
int id = *(int *)arg;
printf("Task %d processed by thread %lu\n", id, pthread_self());
free(arg); // 释放动态分配的内存
usleep(100000); // 模拟任务处理时间
}
int main() {
ThreadPool *pool = thread_pool_create();
// 添加20个任务
for (int i = 0; i < 20; i++) {
int *arg = malloc(sizeof(int));
*arg = i;
thread_pool_add_task(pool, print_task, arg);
}
// 等待所有任务完成
sleep(3);
thread_pool_destroy(pool);
return 0;
}
七、性能考量与最佳实践
7.1 多线程性能优化建议
- 避免过度线程化:线程数通常不应超过CPU核心数的2-4倍
- 减少锁竞争:
- 使用读写锁替代互斥锁(读多写少场景)
- 采用细粒度锁(为不同数据使用不同锁)
- 使用无锁数据结构(如原子操作)
- 避免虚假共享:确保频繁访问的独立数据不在同一缓存行
#include <stdio.h>
#include <pthread.h>
#include <time.h>
#define ARRAY_SIZE 1000000
#define THREAD_COUNT 4
// 有虚假共享的结构体
struct BadStruct {
int a;
int b;
int c;
int d;
};
// 无虚假共享的结构体(使用填充)
struct GoodStruct {
int a;
char padding1[60]; // 假设缓存行大小为64字节
int b;
char padding2[60];
int c;
char padding3[60];
int d;
};
void* worker_bad(void *arg) {
struct BadStruct *data = (struct BadStruct *)arg;
for (int i = 0; i < ARRAY_SIZE; i++) {
data->a++; // 四个线程访问同一缓存行的不同部分
}
return NULL;
}
void* worker_good(void *arg) {
struct GoodStruct *data = (struct GoodStruct *)arg;
for (int i = 0; i < ARRAY_SIZE; i++) {
data->a++; // 每个线程访问独立的缓存行
}
return NULL;
}
int main() {
pthread_t threads[THREAD_COUNT];
struct timespec start, end;
// 测试有虚假共享的情况
struct BadStruct bad_data = {0};
clock_gettime(CLOCK_MONOTONIC, &start);
for (int i = 0; i < THREAD_COUNT; i++) {
pthread_create(&threads[i], NULL, worker_bad, &bad_data);
}
for (int i = 0; i < THREAD_COUNT; i++) {
pthread_join(threads[i], NULL);
}
clock_gettime(CLOCK_MONOTONIC, &end);
double bad_time = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1e9;
// 测试无虚假共享的情况
struct GoodStruct good_data = {0};
clock_gettime(CLOCK_MONOTONIC, &start);
for (int i = 0; i < THREAD_COUNT; i++) {
pthread_create(&threads[i], NULL, worker_good, &good_data);
}
for (int i = 0; i < THREAD_COUNT; i++) {
pthread_join(threads[i], NULL);
}
clock_gettime(CLOCK_MONOTONIC, &end);
double good_time = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1e9;
printf("有虚假共享的运行时间: %.6f秒\n", bad_time);
printf("无虚假共享的运行时间: %.6f秒\n", good_time);
return 0;
}
7.2 多线程编程最佳实践
-
资源管理原则:
- 谁分配谁释放
- 加锁和解锁在同一个函数中进行
- 使用RAII模式管理资源(C中可用
goto
实现类似效果)
-
错误处理:
- 检查所有系统调用的返回值
- 为线程设置取消点
- 实现线程清理处理程序
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
// 线程清理处理程序
void cleanup_handler(void *arg) {
printf("Cleanup handler: releasing mutex\n");
pthread_mutex_unlock((pthread_mutex_t *)arg);
}
void* thread_func(void *arg) {
pthread_mutex_t *mutex = (pthread_mutex_t *)arg;
// 注册清理处理程序
pthread_cleanup_push(cleanup_handler, mutex);
pthread_mutex_lock(mutex);
printf("Thread acquired mutex\n");
// 模拟工作(可能被取消)
sleep(2);
printf("Thread releasing mutex\n");
pthread_mutex_unlock(mutex);
// 注销清理处理程序(非必要,但保持push/pop配对)
pthread_cleanup_pop(0);
return NULL;
}
int main() {
pthread_t thread;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_create(&thread, NULL, thread_func, &mutex);
// 主线程等待一段时间
sleep(1);
// 取消子线程
printf("Main thread cancelling worker thread\n");
pthread_cancel(thread);
// 等待子线程结束
pthread_join(thread, NULL);
// 尝试加锁(验证清理处理程序是否工作)
if (pthread_mutex_trylock(&mutex) == 0) {
printf("Main thread acquired mutex (cleanup worked)\n");
pthread_mutex_unlock(&mutex);
} else {
printf("Mutex still locked (cleanup failed)\n");
}
pthread_mutex_destroy(&mutex);
return 0;
}
八、划重点喽!!!!!
本文全面介绍了C语言多线程编程的各个方面,从基础概念到高级应用,包括:
- 线程创建与管理:pthread_create、pthread_join等基本操作
- 线程同步机制:互斥锁、条件变量、读写锁的使用
- 高级线程控制:线程属性、线程局部存储
- 线程安全与死锁:常见问题与解决方案
- 线程池实现:提高多线程效率的设计模式
- 性能优化:虚假共享、锁竞争等性能问题的处理
多线程编程的核心要点:
- 正确性优先:确保线程安全,避免竞态条件和死锁
- 合理设计:根据任务特性选择适当的同步机制
- 性能考量:平衡线程数量和资源消耗
- 错误处理:妥善处理线程中的异常情况
但是要真正掌握多线程编程,建议各位大佬们:
- 从简单示例开始,逐步构建复杂系统
- 使用工具检测问题(如valgrind、helgrind)
- 阅读优秀的开源多线程代码(如Redis、Nginx)
- 在实践中不断总结经验教训
爱你们吆!!!