Linux多线程编程完全指南

Linux多线程编程完全指南

多线程编程是现代软件开发的核心技能之一。通过多线程,程序可以充分利用多核CPU的并行计算能力,提高系统吞吐量和响应速度。本文将全面解析Linux多线程编程的核心概念、API使用和实战技巧。

一、线程基础概念

1.1 什么是线程

线程是进程内部的执行单元,是CPU调度的基本单位。每个线程包含:

  • 独立的执行栈和寄存器状态
  • 线程局部存储(Thread Local Storage)
  • 调度优先级和策略

同一进程内的多个线程共享:

  • 代码段、数据段、堆内存
  • 文件描述符、信号处理
  • 当前工作目录、用户ID等

1.2 进程与线程的关系

特性进程线程
地址空间独立共享
创建开销大(需复制资源)小(共享资源)
切换开销
通信方式IPC共享内存
安全性高(相互隔离)低(需同步)

1.3 为什么需要多线程

优势

  • 资源共享:线程共享进程资源,通信高效
  • 轻量级:创建和切换开销远小于进程
  • 并行计算:充分利用多核CPU
  • 响应性:后台任务不阻塞主线程

挑战

  • 线程安全问题
  • 竞态条件和死锁
  • 调试复杂度高

二、POSIX线程库(pthread)

2.1 线程创建

#include <pthread.h>

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine)(void *), void *arg);

参数

  • thread:存储线程ID
  • attr:线程属性(NULL表示默认)
  • start_routine:线程函数
  • arg:传递给线程函数的参数

返回值:成功返回0,失败返回错误码

编译选项:需要链接pthread库:-lpthread

2.2 线程创建示例

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

void* thread_func(void* arg) {
    int* p = (int*)arg;
    printf("线程启动,参数: %d\n", *p);
    
    for (int i = 0; i < 5; i++) {
        printf("线程运行中... i=%d\n", i);
        sleep(1);
    }
    
    return NULL;
}

int main() {
    pthread_t tid;
    int arg = 100;
    
    // 创建线程
    if (pthread_create(&tid, NULL, thread_func, &arg) != 0) {
        perror("pthread_create failed");
        return -1;
    }
    
    printf("主线程: 线程ID = %lu\n", tid);
    
    // 等待线程结束
    pthread_join(tid, NULL);
    printf("线程已结束\n");
    
    return 0;
}

2.3 线程参数传递注意事项

错误示例

// 错误:传递局部变量地址
void* thread_func(void* arg) {
    int* p = (int*)arg;
    printf("%d\n", *p);  // 可能访问已释放的内存
}

int main() {
    pthread_t tid;
    for (int i = 0; i < 5; i++) {
        pthread_create(&tid, NULL, thread_func, &i);  // 危险!
    }
}

正确做法1:动态分配

void* thread_func(void* arg) {
    int* p = (int*)arg;
    printf("%d\n", *p);
    free(p);  // 线程负责释放
    return NULL;
}

int main() {
    pthread_t tid;
    for (int i = 0; i < 5; i++) {
        int* p = malloc(sizeof(int));
        *p = i;
        pthread_create(&tid, NULL, thread_func, p);
    }
}

正确做法2:传递值(利用指针大小)

void* thread_func(void* arg) {
    int value = (int)(long)arg;  // 64位系统需要long
    printf("%d\n", value);
    return NULL;
}

int main() {
    pthread_t tid;
    for (int i = 0; i < 5; i++) {
        pthread_create(&tid, NULL, thread_func, (void*)(long)i);
    }
}

2.4 线程等待(pthread_join)

int pthread_join(pthread_t thread, void **retval);

功能:等待指定线程结束,并回收资源。

参数

  • thread:要等待的线程ID
  • retval:存储线程返回值

特性

  • 阻塞调用,直到目标线程结束
  • 只能等待joinable状态的线程
  • 同一线程只能被join一次

获取线程返回值

void* thread_func(void* arg) {
    static int result = 42;
    return &result;  // 或 pthread_exit(&result);
}

int main() {
    pthread_t tid;
    pthread_create(&tid, NULL, thread_func, NULL);
    
    void* ret;
    pthread_join(tid, &ret);
    printf("线程返回值: %d\n", *(int*)ret);
}

2.5 线程退出

方式1:return返回

void* thread_func(void* arg) {
    // 正常返回
    return NULL;
}

方式2:pthread_exit()

void* thread_func(void* arg) {
    pthread_exit(NULL);  // 仅退出当前线程
}

方式3:exit() - 不推荐

void* thread_func(void* arg) {
    exit(0);  // 退出整个进程!所有线程都会终止
}

pthread_exit()详解

#include <pthread.h>

void pthread_exit(void *retval);
  • 仅退出当前线程,不影响其他线程
  • retval会被pthread_join获取
  • 主线程调用pthread_exit()后,其他线程继续运行

2.6 线程分离(detached状态)

线程有两种终止状态:

  • joinable(可结合):默认状态,需要被join回收资源
  • detached(分离):自动回收资源,不能被join

设置分离状态的方式

方式1:创建时设置属性

pthread_t tid;
pthread_attr_t attr;

pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&tid, &attr, thread_func, NULL);
pthread_attr_destroy(&attr);

// 不需要也不可以调用pthread_join

方式2:线程内自我分离

void* thread_func(void* arg) {
    pthread_detach(pthread_self());
    // 线程结束后自动回收
    return NULL;
}

方式3:创建后分离

pthread_t tid;
pthread_create(&tid, NULL, thread_func, NULL);
pthread_detach(tid);  // 分离线程

2.7 线程ID与比较

pthread_t pthread_self(void);  // 获取当前线程ID
int pthread_equal(pthread_t t1, pthread_t t2);  // 比较线程ID

示例

void* thread_func(void* arg) {
    printf("我的线程ID: %lu\n", pthread_self());
    return NULL;
}

int main() {
    pthread_t tid;
    pthread_create(&tid, NULL, thread_func, NULL);
    printf("创建的线程ID: %lu\n", tid);
    
    if (pthread_equal(tid, pthread_self())) {
        printf("这是主线程\n");
    }
}

三、线程安全与数据混乱

3.1 数据混乱的原因

多线程环境下,数据混乱通常由以下原因引起:

  1. 竞态条件(Race Condition):多个线程同时访问共享数据
  2. 原子性缺失:操作被中断,部分完成
  3. 可见性问题:一个线程的修改对其他线程不可见
  4. 指令重排:编译器或CPU优化导致执行顺序改变

3.2 经典案例:卖票问题

#include <stdio.h>
#include <pthread.h>

int tickets = 1000;  // 共享数据

void* sell_ticket(void* arg) {
    while (1) {
        if (tickets > 0) {
            usleep(1000);  // 模拟耗时
            tickets--;
            printf("售出一张票,剩余: %d\n", tickets);
        } else {
            break;
        }
    }
    return NULL;
}

int main() {
    pthread_t t1, t2, t3;
    
    pthread_create(&t1, NULL, sell_ticket, NULL);
    pthread_create(&t2, NULL, sell_ticket, NULL);
    pthread_create(&t3, NULL, sell_ticket, NULL);
    
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_join(t3, NULL);
    
    return 0;
}

问题:可能出现负数票或重复售票!

3.3 线程安全概念

线程安全:多线程环境下,函数或代码在并发执行时能正确处理共享变量,保证数据的一致性和正确性。

线程不安全的常见情况

  • 使用全局或静态变量
  • 修改共享数据没有同步
  • 调用非线程安全函数

保证线程安全的方法

  • 使用同步机制(锁、信号量)
  • 使用线程局部存储
  • 避免共享状态

四、同步与互斥

4.1 基本概念

互斥(Mutual Exclusion)

  • 保证同一时刻只有一个线程访问临界资源
  • 解决"能不能访问"的问题
  • 关注唯一性和排他性

同步(Synchronization)

  • 协调多个线程的执行顺序
  • 解决"什么时候访问"的问题
  • 关注时序性和协作性

关系

  • 互斥是同步的特例
  • 同步包含互斥
  • 同步更复杂,需要条件和状态

4.2 临界区与临界资源

临界资源:一次只允许一个线程访问的共享资源

临界区:访问临界资源的代码段

临界区访问原则

  1. 空闲让进:无人在临界区时,允许进入
  2. 忙则等待:有人时,必须等待
  3. 有限等待:等待时间有限,避免饥饿
  4. 让权等待:等待时释放CPU

五、互斥锁(Mutex)

5.1 互斥锁原理

互斥锁是一个0/1的计数器,表示临界资源是否可访问:

  • 值为1:资源空闲,可以访问
  • 值为0:资源被占用,需要等待

加锁操作是原子操作,不可被中断。

5.2 互斥锁API

#include <pthread.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;  // 静态初始化

int pthread_mutex_init(pthread_mutex_t *mutex, 
                       const pthread_mutexattr_t *attr);  // 动态初始化
int pthread_mutex_destroy(pthread_mutex_t *mutex);  // 销毁

int pthread_mutex_lock(pthread_mutex_t *mutex);     // 加锁(阻塞)
int pthread_mutex_trylock(pthread_mutex_t *mutex);  // 尝试加锁(非阻塞)
int pthread_mutex_unlock(pthread_mutex_t *mutex);   // 解锁

5.3 使用互斥锁解决卖票问题

#include <stdio.h>
#include <pthread.h>

int tickets = 1000;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void* sell_ticket(void* arg) {
    while (1) {
        pthread_mutex_lock(&mutex);  // 加锁
        if (tickets > 0) {
            tickets--;
            printf("线程%lu售出一张票,剩余: %d\n", pthread_self(), tickets);
            pthread_mutex_unlock(&mutex);  // 解锁
            usleep(1000);
        } else {
            pthread_mutex_unlock(&mutex);  // 别忘了解锁!
            break;
        }
    }
    return NULL;
}

int main() {
    pthread_t t1, t2, t3;
    
    pthread_create(&t1, NULL, sell_ticket, NULL);
    pthread_create(&t2, NULL, sell_ticket, NULL);
    pthread_create(&t3, NULL, sell_ticket, NULL);
    
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_join(t3, NULL);
    
    pthread_mutex_destroy(&mutex);
    return 0;
}

5.4 更优雅的写法

void* sell_ticket(void* arg) {
    while (1) {
        pthread_mutex_lock(&mutex);
        int has_ticket = (tickets > 0);
        if (has_ticket) {
            tickets--;
            printf("剩余: %d\n", tickets);
        }
        pthread_mutex_unlock(&mutex);
        
        if (!has_ticket) break;
        usleep(1000);
    }
    return NULL;
}

六、死锁

6.1 死锁定义

死锁是指两个或多个线程互相等待对方释放资源,导致所有线程都无法继续执行。

6.2 死锁产生条件

  1. 互斥条件:资源不能共享
  2. 请求与保持:持有资源同时请求新资源
  3. 不可剥夺:资源不能被强制抢占
  4. 循环等待:存在环路等待链

6.3 死锁示例

#include <stdio.h>
#include <pthread.h>

pthread_mutex_t mutexA = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutexB = PTHREAD_MUTEX_INITIALIZER;

void* thread1(void* arg) {
    pthread_mutex_lock(&mutexA);
    printf("线程1锁住A\n");
    sleep(1);
    pthread_mutex_lock(&mutexB);  // 等待B,但线程2持有B
    printf("线程1锁住B\n");
    pthread_mutex_unlock(&mutexB);
    pthread_mutex_unlock(&mutexA);
    return NULL;
}

void* thread2(void* arg) {
    pthread_mutex_lock(&mutexB);
    printf("线程2锁住B\n");
    sleep(1);
    pthread_mutex_lock(&mutexA);  // 等待A,但线程1持有A
    printf("线程2锁住A\n");
    pthread_mutex_unlock(&mutexA);
    pthread_mutex_unlock(&mutexB);
    return NULL;
}

int main() {
    pthread_t t1, t2;
    pthread_create(&t1, NULL, thread1, NULL);
    pthread_create(&t2, NULL, thread2, NULL);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    return 0;
}

6.4 死锁预防策略

策略1:固定加锁顺序

// 所有线程都按相同顺序加锁
void* thread1(void* arg) {
    pthread_mutex_lock(&mutexA);  // 先A
    pthread_mutex_lock(&mutexB);  // 后B
    // ...
}

void* thread2(void* arg) {
    pthread_mutex_lock(&mutexA);  // 也是先A
    pthread_mutex_lock(&mutexB);  // 后B
    // ...
}

策略2:trylock回退

void* thread1(void* arg) {
    pthread_mutex_lock(&mutexA);
    if (pthread_mutex_trylock(&mutexB) != 0) {
        pthread_mutex_unlock(&mutexA);  // 获取失败,释放A
        // 重试或其他处理
        return NULL;
    }
    // 成功获取两把锁
    pthread_mutex_unlock(&mutexB);
    pthread_mutex_unlock(&mutexA);
    return NULL;
}

策略3:使用层次锁

// 为每个锁分配层级编号,只能从低到高加锁
#define LOCK_LEVEL_A 1
#define LOCK_LEVEL_B 2

// 线程只能先获取level低的锁

七、读写锁

7.1 读写锁特点

读写锁区分读操作和写操作:

  • 读锁(共享锁):多个线程可同时持有
  • 写锁(独占锁):同一时刻只能有一个线程持有

适用场景:读多写少的情况

7.2 读写锁API

#include <pthread.h>

pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;

int pthread_rwlock_init(pthread_rwlock_t *rwlock, NULL);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);   // 加读锁
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);   // 加写锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);   // 解锁

7.3 读写锁示例

#include <stdio.h>
#include <pthread.h>

int data = 0;
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;

void* reader(void* arg) {
    for (int i = 0; i < 10; i++) {
        pthread_rwlock_rdlock(&rwlock);
        printf("读者%lu: data = %d\n", pthread_self(), data);
        pthread_rwlock_unlock(&rwlock);
        usleep(100000);
    }
    return NULL;
}

void* writer(void* arg) {
    for (int i = 0; i < 5; i++) {
        pthread_rwlock_wrlock(&rwlock);
        data++;
        printf("写者%lu: data = %d\n", pthread_self(), data);
        pthread_rwlock_unlock(&rwlock);
        usleep(200000);
    }
    return NULL;
}

int main() {
    pthread_t r1, r2, r3, w1, w2;
    
    pthread_create(&r1, NULL, reader, NULL);
    pthread_create(&r2, NULL, reader, NULL);
    pthread_create(&r3, NULL, reader, NULL);
    pthread_create(&w1, NULL, writer, NULL);
    pthread_create(&w2, NULL, writer, NULL);
    
    pthread_join(r1, NULL);
    pthread_join(r2, NULL);
    pthread_join(r3, NULL);
    pthread_join(w1, NULL);
    pthread_join(w2, NULL);
    
    return 0;
}

八、条件变量

8.1 条件变量作用

条件变量用于线程间的等待/通知机制:

  • 一个线程等待某个条件成立
  • 另一个线程改变条件并通知等待者

8.2 条件变量API

#include <pthread.h>

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

int pthread_cond_init(pthread_cond_t *cond, NULL);
int pthread_cond_destroy(pthread_cond_t *cond);

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_signal(pthread_cond_t *cond);   // 唤醒一个等待线程
int pthread_cond_broadcast(pthread_cond_t *cond); // 唤醒所有等待线程

8.3 生产者-消费者模型

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>

#define BUFFER_SIZE 10

int buffer[BUFFER_SIZE];
int count = 0;
int in = 0, out = 0;

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t not_full = PTHREAD_COND_INITIALIZER;
pthread_cond_t not_empty = PTHREAD_COND_INITIALIZER;

void* producer(void* arg) {
    for (int i = 0; i < 20; i++) {
        pthread_mutex_lock(&mutex);
        
        while (count == BUFFER_SIZE) {
            pthread_cond_wait(&not_full, &mutex);
        }
        
        buffer[in] = i;
        in = (in + 1) % BUFFER_SIZE;
        count++;
        printf("生产: %d, 缓冲区: %d\n", i, count);
        
        pthread_cond_signal(&not_empty);
        pthread_mutex_unlock(&mutex);
        usleep(100000);
    }
    return NULL;
}

void* consumer(void* arg) {
    for (int i = 0; i < 20; i++) {
        pthread_mutex_lock(&mutex);
        
        while (count == 0) {
            pthread_cond_wait(&not_empty, &mutex);
        }
        
        int item = buffer[out];
        out = (out + 1) % BUFFER_SIZE;
        count--;
        printf("消费: %d, 缓冲区: %d\n", item, count);
        
        pthread_cond_signal(&not_full);
        pthread_mutex_unlock(&mutex);
        usleep(150000);
    }
    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);
    return 0;
}

8.4 条件变量使用规范

等待方

pthread_mutex_lock(&mutex);
while (condition_is_false) {  // 使用while而非if!
    pthread_cond_wait(&cond, &mutex);
}
// 条件成立,执行操作
pthread_mutex_unlock(&mutex);

通知方

pthread_mutex_lock(&mutex);
// 改变条件
condition_is_false = false;
pthread_cond_signal(&cond);  // 或 pthread_cond_broadcast
pthread_mutex_unlock(&mutex);

为什么用while而不是if

  • 防止虚假唤醒
  • 条件可能在唤醒后再次改变

九、信号量

9.1 POSIX信号量

#include <semaphore.h>

sem_t sem;

int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_destroy(sem_t *sem);
int sem_wait(sem_t *sem);   // P操作,减1
int sem_post(sem_t *sem);   // V操作,加1

9.2 信号量实现生产者-消费者

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>

#define BUFFER_SIZE 10

int buffer[BUFFER_SIZE];
int in = 0, out = 0;

sem_t empty;  // 空槽位数
sem_t full;   // 数据个数
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void* producer(void* arg) {
    for (int i = 0; i < 20; i++) {
        sem_wait(&empty);  // 等待空槽位
        pthread_mutex_lock(&mutex);
        
        buffer[in] = i;
        in = (in + 1) % BUFFER_SIZE;
        printf("生产: %d\n", i);
        
        pthread_mutex_unlock(&mutex);
        sem_post(&full);  // 增加数据计数
        usleep(100000);
    }
    return NULL;
}

void* consumer(void* arg) {
    for (int i = 0; i < 20; i++) {
        sem_wait(&full);  // 等待数据
        pthread_mutex_lock(&mutex);
        
        int item = buffer[out];
        out = (out + 1) % BUFFER_SIZE;
        printf("消费: %d\n", item);
        
        pthread_mutex_unlock(&mutex);
        sem_post(&empty);  // 增加空槽位
        usleep(150000);
    }
    return NULL;
}

int main() {
    sem_init(&empty, 0, BUFFER_SIZE);
    sem_init(&full, 0, 0);
    
    pthread_t prod, cons;
    pthread_create(&prod, NULL, producer, NULL);
    pthread_create(&cons, NULL, consumer, NULL);
    pthread_join(prod, NULL);
    pthread_join(cons, NULL);
    
    sem_destroy(&empty);
    sem_destroy(&full);
    return 0;
}

十、总结

Linux多线程编程的核心要点:

  1. 线程基础:理解进程与线程的关系,掌握创建、等待、退出操作
  2. 线程安全:识别竞态条件,使用同步机制保护共享资源
  3. 互斥锁:最常用的同步机制,保护临界区
  4. 死锁预防:固定加锁顺序,使用trylock
  5. 读写锁:适用于读多写少场景
  6. 条件变量:实现线程间等待/通知
  7. 信号量:灵活的同步工具,可控制并发数

最佳实践

  • 最小化临界区范围
  • 使用RAII管理锁
  • 避免嵌套锁
  • 合理设置线程数量(CPU核心数 × 2 左右)
  • 使用线程池避免频繁创建销毁
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值