当前位置: 首页 > news >正文

Linux C线程编程全指南

Linux C语言线程编程详解

一、POSIX 线程基础

1.1 什么是线程?

  • 线程(Thread)是进程内的执行单元,是CPU调度的基本单位

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

    • 进程代码段
    • 数据段(全局变量)
    • 打开的文件描述符
    • 信号处理器
    • 当前工作目录
    • 用户ID和组ID
  • 每个线程独有:

    • 线程ID
    • 寄存器状态(包括程序计数器和栈指针)
    • 栈空间
    • errno变量
    • 信号屏蔽字
    • 优先级

1.2 编译链接

使用pthread库需要链接 -lpthread:

gcc -o program program.c -lpthread

头文件:

#include <pthread.h>

二、核心函数详解

2.1 pthread_create() - 创建线程

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

参数详解:

  1. pthread_t *thread

    • 指向线程标识符的指针
    • 函数成功返回后,新线程的ID会被写入该指针指向的内存
    • pthread_t 是一个不透明类型,不要直接操作其内容
  2. const pthread_attr_t *attr

    • 线程属性对象指针
    • 传入 NULL 使用默认属性
    • 可通过 pthread_attr_init() 初始化后设置:
      • 分离状态(detached/joinable)
      • 栈大小
      • 调度策略
      • 调度优先级
  3. void *(*start_routine)(void *)

    • 线程启动函数指针
    • 函数签名必须是: void* function_name(void* arg)
    • 线程从这个函数开始执行
    • 函数返回时线程终止
  4. void *arg

    • 传递给启动函数的参数
    • 可以传递任何类型的指针(需要类型转换)
    • 传递多个参数时通常使用结构体

返回值:

  • 成功返回 0
  • 失败返回错误码(不设置errno):
    • EAGAIN: 系统资源不足
    • EINVAL: 无效的属性设置
    • EPERM: 无权限设置调度策略

示例:

void *thread_func(void *arg) {int id = *(int *)arg;printf("Thread %d running\n", id);return NULL;
}int main() {pthread_t tid;int thread_id = 1;if (pthread_create(&tid, NULL, thread_func, &thread_id) != 0) {perror("pthread_create failed");return 1;}pthread_join(tid, NULL);return 0;
}

2.2 pthread_join() - 等待线程结束

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

参数详解:

  1. pthread_t thread

    • 要等待的线程ID
    • 必须是joinable状态的线程
    • 不能重复join同一个线程
  2. void **retval

    • 指向指针的指针,用于接收线程返回值
    • 传入 NULL 表示不关心返回值
    • 线程通过 returnpthread_exit() 返回值

返回值:

  • 成功返回 0
  • 失败返回错误码:
    • EDEADLK: 检测到死锁
    • EINVAL: 线程不是joinable的
    • ESRCH: 找不到该线程

示例:

void *thread_func(void *arg) {int *result = malloc(sizeof(int));*result = 42;return result;
}int main() {pthread_t tid;void *ret_val;pthread_create(&tid, NULL, thread_func, NULL);pthread_join(tid, &ret_val);printf("Thread returned: %d\n", *(int *)ret_val);free(ret_val);return 0;
}

2.3 pthread_exit() - 线程退出

void pthread_exit(void *retval);

参数详解:

  1. void *retval
    • 线程退出状态,可被 pthread_join() 获取
    • 不要返回指向线程栈上的局部变量的指针
    • 传入 NULL 表示无返回值

注意事项:

  • 如果主线程调用 pthread_exit(),其他线程会继续运行
  • 如果主线程从 main() return,整个进程结束

2.4 pthread_self() - 获取当前线程ID

pthread_t pthread_self(void);

返回调用线程的ID,总是成功。


2.5 pthread_detach() - 分离线程

int pthread_detach(pthread_t thread);

参数详解:

  1. pthread_t thread
    • 要分离的线程ID
    • 可以是自己 pthread_detach(pthread_self())

作用:

  • 将线程标记为detached状态
  • 线程结束后资源自动释放,无需join
  • detached的线程不能再被join

2.6 pthread_cancel() - 取消线程

int pthread_cancel(pthread_t thread);

向指定线程发送取消请求,但不会立即终止线程。线程需要到达取消点才会响应。


三、线程同步

3.1 互斥锁 (Mutex)

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;// 初始化
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);// 加锁
int pthread_mutex_lock(pthread_mutex_t *mutex);// 尝试加锁(非阻塞)
int pthread_mutex_trylock(pthread_mutex_t *mutex);// 解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);// 销毁
int pthread_mutex_destroy(pthread_mutex_t *mutex);

3.2 条件变量 (Condition Variable)

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;// 初始化
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);// 等待条件
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);// 销毁
int pthread_cond_destroy(pthread_cond_t *cond);

四、常见陷阱和注意事项

4.1 ⚠️ 传递参数的陷阱

错误示例:

// 危险!循环变量的地址
for (int i = 0; i < 5; i++) {pthread_create(&tid[i], NULL, thread_func, &i);  // ❌
}

问题: 所有线程可能读取到相同的值,因为 i 的地址是同一个。

正确做法:

// 方法1: 为每个线程分配独立内存
for (int i = 0; i < 5; i++) {int *arg = malloc(sizeof(int));*arg = i;pthread_create(&tid[i], NULL, thread_func, arg);
}// 方法2: 使用数组
int args[5];
for (int i = 0; i < 5; i++) {args[i] = i;pthread_create(&tid[i], NULL, thread_func, &args[i]);
}// 方法3: 直接传值(强制类型转换,仅适用于小整数)
for (int i = 0; i < 5; i++) {pthread_create(&tid[i], NULL, thread_func, (void *)(intptr_t)i);
}

4.2 ⚠️ 返回栈上的指针

错误示例:

void *thread_func(void *arg) {int result = 42;return &result;  // ❌ 返回局部变量地址
}

正确做法:

// 方法1: 动态分配内存
void *thread_func(void *arg) {int *result = malloc(sizeof(int));*result = 42;return result;  // 调用者负责释放
}// 方法2: 使用全局变量或静态变量
void *thread_func(void *arg) {static int result = 42;return &result;
}

4.3 ⚠️ 忘记Join或Detach

问题: 如果线程是joinable状态但从未被join,会导致资源泄漏。

正确做法:

// 要么join
pthread_join(tid, NULL);// 要么detach
pthread_detach(tid);// 或者创建时就设置为detached
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);

4.4 ⚠️ 主线程过早退出

错误示例:

int main() {pthread_t tid;pthread_create(&tid, NULL, thread_func, NULL);return 0;  // ❌ 主线程退出,整个进程结束
}

正确做法:

int main() {pthread_t tid;pthread_create(&tid, NULL, thread_func, NULL);pthread_join(tid, NULL);  // 等待线程结束return 0;
}// 或者主线程调用pthread_exit
int main() {pthread_t tid;pthread_create(&tid, NULL, thread_func, NULL);pthread_exit(NULL);  // 主线程退出,但进程继续
}

4.5 ⚠️ 竞态条件 (Race Condition)

错误示例:

int counter = 0;void *thread_func(void *arg) {for (int i = 0; i < 100000; i++) {counter++;  // ❌ 非原子操作}return NULL;
}

正确做法:

int counter = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;void *thread_func(void *arg) {for (int i = 0; i < 100000; i++) {pthread_mutex_lock(&mutex);counter++;pthread_mutex_unlock(&mutex);}return NULL;
}

4.6 ⚠️ 死锁 (Deadlock)

常见死锁场景:

  1. 重复加锁
pthread_mutex_lock(&mutex);
pthread_mutex_lock(&mutex);  // ❌ 死锁
  1. 循环等待
// 线程1
pthread_mutex_lock(&mutex1);
pthread_mutex_lock(&mutex2);// 线程2
pthread_mutex_lock(&mutex2);  // 可能死锁
pthread_mutex_lock(&mutex1);

避免死锁:

  • 按固定顺序获取锁
  • 使用 pthread_mutex_trylock()
  • 设置超时
  • 避免嵌套锁

4.7 ⚠️ 条件变量的虚假唤醒

错误示例:

pthread_cond_wait(&cond, &mutex);
// 假设条件满足

正确做法:

while (!condition_satisfied) {pthread_cond_wait(&cond, &mutex);
}
// 现在条件确实满足

使用 while 而不是 if 来检查条件。


4.8 ⚠️ 编译链接错误

常见错误:

undefined reference to `pthread_create`

解决方法:

gcc program.c -o program -lpthread  # 注意 -lpthread 必须在最后

五、实用技巧

5.1 获取CPU核心数

#include <unistd.h>int num_cores = sysconf(_SC_NPROCESSORS_ONLN);

5.2 线程数组管理

#define NUM_THREADS 4pthread_t threads[NUM_THREADS];// 创建
for (int i = 0; i < NUM_THREADS; i++) {pthread_create(&threads[i], NULL, thread_func, &args[i]);
}// 等待
for (int i = 0; i < NUM_THREADS; i++) {pthread_join(threads[i], NULL);
}

5.3 线程局部存储

__thread int thread_local_var = 0;  // 每个线程独立的变量

六、完整示例

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>#define NUM_THREADS 5typedef struct {int id;int iterations;
} thread_data_t;pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int shared_counter = 0;void *worker_thread(void *arg) {thread_data_t *data = (thread_data_t *)arg;printf("Thread %d started\n", data->id);for (int i = 0; i < data->iterations; i++) {pthread_mutex_lock(&mutex);shared_counter++;pthread_mutex_unlock(&mutex);usleep(10000);  // 模拟工作}printf("Thread %d finished\n", data->id);free(data);  // 释放传入的参数return NULL;
}int main() {pthread_t threads[NUM_THREADS];// 创建线程for (int i = 0; i < NUM_THREADS; i++) {thread_data_t *data = malloc(sizeof(thread_data_t));data->id = i;data->iterations = 100;if (pthread_create(&threads[i], NULL, worker_thread, data) != 0) {fprintf(stderr, "Error creating thread %d\n", i);free(data);exit(1);}}// 等待所有线程完成for (int i = 0; i < NUM_THREADS; i++) {pthread_join(threads[i], NULL);}printf("All threads completed. Counter = %d\n", shared_counter);pthread_mutex_destroy(&mutex);return 0;
}

七、调试技巧

7.1 检测竞态条件

使用 Thread Sanitizer:

gcc -fsanitize=thread -g program.c -o program -lpthread
./program

7.2 使用GDB调试多线程

gdb ./program
(gdb) break thread_func
(gdb) run
(gdb) info threads          # 查看所有线程
(gdb) thread 2              # 切换到线程2
(gdb) where                 # 查看当前线程的调用栈

八、性能考虑

  1. 避免过度加锁: 锁的粒度要合适,太细影响性能,太粗降低并发
  2. 线程数量: 通常设置为CPU核心数或核心数的倍数
  3. 避免频繁创建销毁线程: 考虑使用线程池
  4. 减少锁竞争: 使用无锁数据结构或读写锁

九、Linux线程底层常识

9.1 线程实现模型

Linux线程的本质:

  • Linux内核并不区分线程和进程,统一称为"任务"(task)
  • 线程是通过 clone() 系统调用创建的轻量级进程(LWP)
  • POSIX线程是在用户空间实现的,通过NPTL(Native POSIX Thread Library)

1:1 线程模型:

每个用户线程 <---> 一个内核线程(LWP)
  • Linux采用1:1模型,每个pthread对应一个内核调度实体
  • 优点: 真正的并行执行,阻塞不影响其他线程
  • 缺点: 创建开销较大,线程切换有内核开销

9.2 线程ID的真相

两种线程ID:

  1. pthread_t (POSIX线程ID)

    pthread_t tid = pthread_self();  // 用户空间的线程标识符
    
    • 不透明类型,可能是结构体、整数或指针
    • 仅在进程内有效,不同进程中的值可能相同
    • 用于pthread_*函数
  2. LWP ID (轻量级进程ID / 内核线程ID)

    #include <sys/syscall.h>
    pid_t tid = syscall(SYS_gettid);  // 内核中的线程ID
    
    • 系统范围内唯一的整数
    • 可用于CPU亲和性设置、调度策略等
    • top命令显示的就是LWP ID

易错点:

// ❌ 错误: 不能直接打印pthread_t
printf("%d", pthread_self());  // ✅ 正确: 获取内核线程ID
printf("LWP ID: %ld", (long)syscall(SYS_gettid));// ✅ 或者比较pthread_t
if (pthread_equal(tid1, tid2)) { ... }

9.3 线程栈的秘密

默认栈大小:

#include <pthread.h>
#include <sys/resource.h>// 查看默认栈大小
pthread_attr_t attr;
size_t stacksize;
pthread_attr_init(&attr);
pthread_attr_getstacksize(&attr, &stacksize);
printf("Default stack: %zu bytes\n", stacksize);  // 通常是8MB

栈溢出陷阱:

void *thread_func(void *arg) {char buffer[10 * 1024 * 1024];  // ❌ 10MB数组,可能栈溢出// ... 程序可能崩溃或产生段错误
}

正确做法:

// 方法1: 使用堆内存
void *thread_func(void *arg) {char *buffer = malloc(10 * 1024 * 1024);// 使用buffer...free(buffer);
}// 方法2: 增加栈大小
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setstacksize(&attr, 16 * 1024 * 1024);  // 16MB
pthread_create(&tid, &attr, thread_func, NULL);
pthread_attr_destroy(&attr);

栈地址查看:

void *thread_func(void *arg) {int local_var;printf("Stack address: %p\n", (void*)&local_var);pthread_attr_t attr;void *stackaddr;size_t stacksize;pthread_getattr_np(pthread_self(), &attr);pthread_attr_getstack(&attr, &stackaddr, &stacksize);printf("Stack: %p - %p (size: %zu)\n", stackaddr, (char*)stackaddr + stacksize, stacksize);pthread_attr_destroy(&attr);
}

9.4 线程调度与优先级

调度策略:

Linux支持三种实时调度策略:

  • SCHED_FIFO: 先进先出实时调度
  • SCHED_RR: 时间片轮转实时调度
  • SCHED_OTHER: 普通分时调度(默认)
#include <sched.h>// 设置线程调度策略和优先级
pthread_attr_t attr;
struct sched_param param;pthread_attr_init(&attr);
pthread_attr_setschedpolicy(&attr, SCHED_FIFO);
param.sched_priority = 50;  // 1-99,数字越大优先级越高
pthread_attr_setschedparam(&attr, &param);
pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);pthread_create(&tid, &attr, thread_func, NULL);

陷阱:

// ❌ 忘记设置PTHREAD_EXPLICIT_SCHED,调度策略可能被忽略
pthread_attr_setschedpolicy(&attr, SCHED_FIFO);
// 需要root权限或CAP_SYS_NICE能力

9.5 CPU亲和性 (CPU Affinity)

将线程绑定到特定CPU核心:

#define _GNU_SOURCE
#include <sched.h>
#include <pthread.h>void set_cpu_affinity(int cpu_id) {cpu_set_t cpuset;CPU_ZERO(&cpuset);CPU_SET(cpu_id, &cpuset);pthread_t current = pthread_self();pthread_setaffinity_np(current, sizeof(cpu_set_t), &cpuset);
}void *thread_func(void *arg) {int cpu = *(int*)arg;set_cpu_affinity(cpu);  // 绑定到指定CPU// 验证绑定cpu_set_t cpuset;CPU_ZERO(&cpuset);pthread_getaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset);for (int i = 0; i < CPU_SETSIZE; i++) {if (CPU_ISSET(i, &cpuset)) {printf("Thread running on CPU %d\n", i);}}
}

应用场景:

  • 高性能计算,减少缓存失效
  • 实时系统,避免线程迁移
  • NUMA架构优化

9.6 信号处理的坑

问题: 信号会被任意线程接收

// ❌ 危险: 信号可能发送给任意线程
void signal_handler(int sig) {printf("Caught signal in thread %ld\n", pthread_self());
}int main() {signal(SIGINT, signal_handler);// 创建多个线程...// Ctrl+C 时,不确定哪个线程会处理信号
}

正确做法: 屏蔽信号,专门线程处理

#include <signal.h>void *signal_thread(void *arg) {sigset_t *set = (sigset_t *)arg;int sig;while (1) {sigwait(set, &sig);  // 等待信号printf("Signal %d received by dedicated thread\n", sig);if (sig == SIGINT) break;}return NULL;
}int main() {sigset_t set;pthread_t sig_tid;// 1. 阻塞所有线程的信号sigemptyset(&set);sigaddset(&set, SIGINT);sigaddset(&set, SIGTERM);pthread_sigmask(SIG_BLOCK, &set, NULL);// 2. 创建工作线程(继承信号屏蔽字)// pthread_create(...);// 3. 创建专门的信号处理线程pthread_create(&sig_tid, NULL, signal_thread, &set);pthread_join(sig_tid, NULL);return 0;
}

9.7 线程取消的陷阱

取消点 (Cancellation Point):

并非所有函数都是取消点,线程不会立即取消:

void *thread_func(void *arg) {// 这个循环可能无法被取消!while (1) {// 密集计算,没有取消点}
}// 主线程
pthread_cancel(tid);  // 发送取消请求
// 但线程可能永远不会取消!

解决方法:

// 方法1: 手动添加取消点
void *thread_func(void *arg) {while (1) {pthread_testcancel();  // 显式取消点// 做一些工作...}
}// 方法2: 使用异步取消(危险!)
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);// 方法3: 使用标志位(推荐)
volatile int stop_flag = 0;void *thread_func(void *arg) {while (!stop_flag) {// 工作...}
}// 主线程
stop_flag = 1;
pthread_join(tid, NULL);

取消清理处理器:

void cleanup_handler(void *arg) {printf("Cleanup: releasing resource %p\n", arg);free(arg);
}void *thread_func(void *arg) {void *resource = malloc(1024);pthread_cleanup_push(cleanup_handler, resource);  // 注册清理函数// 可能被取消的代码...pthread_testcancel();pthread_cleanup_pop(1);  // 1=执行清理函数, 0=不执行return NULL;
}

9.8 fork() 与多线程的危险组合

重大陷阱: 多线程程序中调用fork()

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int counter = 0;void *thread_func(void *arg) {while (1) {pthread_mutex_lock(&mutex);counter++;pthread_mutex_unlock(&mutex);usleep(1000);}
}int main() {pthread_t tid;pthread_create(&tid, NULL, thread_func, NULL);sleep(1);pid_t pid = fork();  // ⚠️ 危险!if (pid == 0) {// 子进程pthread_mutex_lock(&mutex);  // 可能永远死锁!printf("Child: %d\n", counter);pthread_mutex_unlock(&mutex);}
}

问题原因:

  1. fork()后,子进程只复制调用线程,其他线程消失
  2. 如果某个锁在fork时被其他线程持有,子进程中该锁永远无法释放
  3. 子进程继承了父进程的锁状态,但持有锁的线程已不存在

解决方案:

// 方法1: fork前准备,fork后清理
pthread_atfork(prepare, parent, child);void prepare(void) {pthread_mutex_lock(&mutex);  // fork前获取所有锁
}void parent(void) {pthread_mutex_unlock(&mutex);  // 父进程释放锁
}void child(void) {pthread_mutex_unlock(&mutex);  // 子进程释放锁
}// 方法2: 子进程fork后立即exec(推荐)
if (fork() == 0) {execl("/bin/ls", "ls", NULL);  // 不使用任何父进程资源exit(1);
}// 方法3: 避免在多线程程序中使用fork

9.9 errno 与线程安全

errno 是线程局部的:

#include <errno.h>void *thread_func(void *arg) {int fd = open("/nonexistent", O_RDONLY);if (fd < 0) {printf("Thread %ld: errno = %d\n", pthread_self(), errno);  // 每个线程独立的errno}
}

但并非所有函数都是线程安全的:

// ❌ 非线程安全函数
strtok()     // 使用静态缓冲区
localtime()  // 返回静态结构
gethostbyname()// ✅ 线程安全替代版本
strtok_r()   // reentrant版本
localtime_r()
gethostbyname_r()

识别线程安全函数:

  • 查看man手册的"ATTRIBUTES"部分
  • 通常 _r 后缀表示可重入(reentrant)版本

9.10 内存模型与可见性

问题: 编译器和CPU可能重排序指令

int data = 0;
int flag = 0;// 线程1
void *writer(void *arg) {data = 42;      // ⚠️ 可能被重排序flag = 1;       // 线程2可能先看到flag=1,但data还是0
}// 线程2
void *reader(void *arg) {while (flag == 0) ;  // 自旋等待printf("Data: %d\n", data);  // 可能打印0而不是42!
}

解决方案: 使用内存屏障或锁

// 方法1: 使用互斥锁(隐含内存屏障)
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;void *writer(void *arg) {pthread_mutex_lock(&mutex);data = 42;flag = 1;pthread_mutex_unlock(&mutex);  // 确保可见性
}// 方法2: 使用C11原子操作
#include <stdatomic.h>
atomic_int flag = 0;void *writer(void *arg) {data = 42;atomic_store(&flag, 1);  // release语义
}void *reader(void *arg) {while (atomic_load(&flag) == 0) ;  // acquire语义printf("Data: %d\n", data);
}// 方法3: volatile + 内存屏障(GCC)
volatile int flag = 0;void *writer(void *arg) {data = 42;__sync_synchronize();  // 内存屏障flag = 1;
}

9.11 线程局部存储 (TLS)

三种TLS实现方式:

// 方法1: __thread关键字(GNU C扩展,最高效)
__thread int thread_id = 0;void *thread_func(void *arg) {thread_id = *(int*)arg;  // 每个线程独立printf("Thread %d\n", thread_id);
}// 方法2: pthread_key_t(POSIX标准,可添加析构函数)
pthread_key_t key;void destructor(void *value) {free(value);
}int main() {pthread_key_create(&key, destructor);// 每个线程中int *data = malloc(sizeof(int));pthread_setspecific(key, data);int *retrieved = pthread_getspecific(key);
}// 方法3: C11 thread_local关键字
#include <threads.h>
thread_local int counter = 0;

应用场景:

  • 线程特定的错误码
  • 每线程的随机数生成器状态
  • 数据库连接池中的连接
  • 性能计数器

9.12 线程数量的权衡

不是越多越好!

// ❌ 创建过多线程
for (int i = 0; i < 10000; i++) {pthread_create(&tids[i], NULL, task, NULL);
}
// 问题: 上下文切换开销巨大,内存消耗严重

经验法则:

  1. CPU密集型任务: 线程数 = CPU核心数 + 1

    int ncores = sysconf(_SC_NPROCESSORS_ONLN);
    int nthreads = ncores + 1;
    
  2. IO密集型任务: 可以更多,通常2-4倍核心数

    int nthreads = ncores * 2;
    
  3. 使用线程池: 预创建固定数量的线程,复用执行任务

监控线程数量:

# 查看进程的线程数
ps -eLf | grep process_name | wc -l# 或使用top
top -H -p <pid># 查看线程详情
cat /proc/<pid>/status | grep Threads

9.13 调试工具

Valgrind - Helgrind (检测竞态条件)

valgrind --tool=helgrind ./program

Thread Sanitizer (检测数据竞争)

gcc -fsanitize=thread -g program.c -o program -lpthread
./program

GDB多线程调试

gdb ./program
(gdb) set scheduler-locking on  # 只运行当前线程
(gdb) thread apply all bt       # 所有线程的调用栈
(gdb) info threads              # 线程列表

SystemTap查看线程活动

stap -e 'probe process("/path/to/program").thread.* { println(pp()) }'

十、性能优化技巧

10.1 减少锁竞争

// ❌ 粗粒度锁
pthread_mutex_lock(&big_lock);
process_data_1();
process_data_2();
pthread_mutex_unlock(&big_lock);// ✅ 细粒度锁
pthread_mutex_lock(&lock1);
process_data_1();
pthread_mutex_unlock(&lock1);pthread_mutex_lock(&lock2);
process_data_2();
pthread_mutex_unlock(&lock2);

10.2 读写锁

pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;// 读多写少的场景
void *reader(void *arg) {pthread_rwlock_rdlock(&rwlock);  // 多个读者可并发// 读取数据pthread_rwlock_unlock(&rwlock);
}void *writer(void *arg) {pthread_rwlock_wrlock(&rwlock);  // 独占写入// 修改数据pthread_rwlock_unlock(&rwlock);
}

10.3 自旋锁 vs 互斥锁

// 短期持有 + 低竞争 -> 自旋锁
pthread_spinlock_t spinlock;
pthread_spin_init(&spinlock, 0);
pthread_spin_lock(&spinlock);
// 快速操作
pthread_spin_unlock(&spinlock);// 长期持有 + 高竞争 -> 互斥锁
pthread_mutex_lock(&mutex);
// 耗时操作
pthread_mutex_unlock(&mutex);

十一、总结

核心原则:

✅ 编译必须链接 -lpthread
✅ 理解线程=LWP,pthread_t≠内核TID
✅ 栈大小有限,大数组用堆
✅ errno是线程局部的,但非所有函数线程安全
✅ 信号在多线程中要专门处理
✅ 多线程+fork=灾难,避免或用pthread_atfork
✅ 内存可见性需要同步机制保证
✅ 线程取消不一定立即生效
✅ 线程数量要根据任务类型和CPU核心数权衡
✅ 使用工具检测竞态和死锁

调试检查清单:

  • 编译时启用 -Wall -Wextra
  • 使用ThreadSanitizer检测数据竞争
  • 使用Helgrind检测同步错误
  • 检查所有共享变量是否有锁保护
  • 确认joinable线程都被join或detach
  • 验证没有返回栈上变量地址
  • 测试长时间运行是否有资源泄漏
  • 压力测试多线程并发场景

记住: 并发编程的难点不在于创建线程,而在于正确地协调它们!

http://www.dtcms.com/a/607114.html

相关文章:

  • 江门seo网站排名中文商城响应式html网站模板
  • 锁的初步学习
  • 淘宝网站建设的优点大连高端模板建站
  • 国外 网站源码wordpress新建页面不显示
  • locust压测如何展开
  • wordpress整站搬迁网站建设需要干什么
  • 江西建设职业技能教育咨询网站汕头市作风建设的网站
  • 使用socket实现TCP服务端
  • codeforcesB. Siga ta Kymata
  • 山东网站建设的方案ps软件推荐
  • 网站建设的想法佛山网络营销推广
  • 燃气公司网站建设方案中国三大生产建设兵团
  • 【C++进阶】C++11
  • 昌邑住房和城乡建设局网站怎么用sharepoint做网站
  • wordpress直播网站主题中国室内设计网官网总裁
  • 网络建站东北深圳公司网站建设
  • C++ 数组:基础与进阶全解析
  • 化妆品手机端网站模板织梦网站模板视频
  • 长沙优化网站技巧wordpress邮箱配置stmp
  • leetcode 3228 将1移动到末尾的最大操作次数
  • 贵州最好的网站建设推广公司天津建设
  • 能源企业 网站建设学校门户网站建设的优势
  • RunLoop 深度解析
  • 如何来建设网站青岛建设集团招工信息网站
  • 1688采购系统:批量下单自动下单功能实现
  • 网站服务器cpu占用多少要升级工业信息化部网站备案
  • 手机网站模块一直免费的服务器下载
  • 实战:爬取汽车之家车型参数对比的技术指南
  • 网站后台怎么控制护理专业简历制作
  • DP 转光纤:捷米特 JM-DP-FIBER-S-A/B-R 转换器汽车焊接产线应用案例