C语言多线程编程全面精讲:从小白到专家(附示例源码)

一、多线程编程基础概念

1.1 线程与进程的区别

在开始多线程编程前,我们需要明确线程(Thread)和进程(Process)的关键区别:

进程:操作系统资源分配的基本单位,拥有独立的地址空间线程:CPU调度的基本单位,共享进程的地址空间

/*

* 进程 vs 线程对比:

*

* | 特性 | 进程 | 线程 |

* |---------------|------------------------|------------------------|

* | 内存空间 | 独立 | 共享 |

* | 创建开销 | 大 | 小 |

* | 通信方式 | 管道、消息队列、共享内存 | 全局变量、互斥锁等 |

* | 上下文切换 | 慢 | 快 |

* | 安全性 | 高(隔离) | 低(共享内存易冲突) |

*/

1.2 POSIX线程标准

C语言本身不包含多线程支持,我们使用POSIX线程(Pthreads)标准:

#include // 必须包含的头文件

// 编译时需要链接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

#include

#include

// 线程函数

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

#include

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

#include

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

#include

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

#include

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

#include

#include

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

#include

#include

// 定义线程局部变量

__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

#include

// 非线程安全的伪随机数生成器

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

#include

// 线程安全的伪随机数生成器

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

#include

#include

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

#include

#include

#include

#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

#include

#include

#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

#include

#include

// 线程清理处理程序

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)在实践中不断总结经验教训

爱你们吆!!!