pthread 入门

pthread 入门

Author: ChrisZZ
Link: https://www.cnblogs.com/zjutzz
Time: 2024-05-03 23:55:21

0. 目的

pthread 是C接口的多线程库,使用广泛:linux-x64, macOS, android, 各种车载嵌入式的 linux 系统。

使用 pthread 可以做多线程计算,提高程序整体效率。

不使用C11或C++11的原因: C 比较简洁, 有些手机平台的 C++11 多线程据说有bug。

网络上搜索 “C 多线程”,靠前的结果总是被 “muduo网络” 和 “C++11多线程” 的资料覆盖, 专门搜 pthread 发现很多博客只写了一两个技术点,还不乏错误写法。

本篇拨乱反正。

1. 概念

  • 互斥锁 mutex: 用来保护变量的排他性访问
  • 临界区 critical section: 只应当被单个线程执行的代码片段.
  • 数据竞争 data race: >=2 个线程函数访问变量,并且其中至少有一个线程在修改它
  • 线程泄漏 thread leak: 线程创建后没有被回收,例如子线程创建后没被主线程等待,也没自行detach
  • 条件变量 condition variable: 用于线程同步,通常一个普通变量搭配使用

2. pthread API

在线文档 https://pubs.opengroup.org/onlinepubs/7908799/xsh/pthread.h.html

本地文档 man 3 pthreat_create

弄懂最常用的几个API可以解决不少问题了:

#include <pthread.h>

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

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_unlock(pthread_mutex_t* mutex);
pthread_mutex_t cond = PTHREAD_MUTEX_INITIALIZER;

int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t* attr);
int pthread_cond_destroy(pthread_cond_t* cond);
int pthread_cond_signal(pthread_cond_t* cond);
int pthread_cond_broadcast(pthread_cond_t* cond);
int pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex);
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

int pthread_t pthread_self(void);

3. 用 tsan 检查线程问题

首先编译链接时开启tsan:

gcc xxx.c -g -fsanitize=thread

运行程序,查看tsan给出的报告,主要两类:

  • data race:数据竞争
  • thread leak: 线程泄漏

4. 虚假唤醒

使用条件变量时通常要这样写:

pthread_mutex_lock(&mutex);
while (determine(xxx))
{
    pthread_cond_wait(&cond, &mutex);
}
change(xxx);
pthread_mutex_unlock(&mutex);

用 while 而不是 if, 是为了避免虚假唤醒。两类虚假唤醒:

  • 操作系统让 pthrad_cond_wait 醒来, 并没有别的线程发送 signal。 在Linux Pthread 上这其实不可能发生
  • 有多个子线程,每个子线程都执行上述代码,如果有>=2个子线程,可能第一个线程在执行后,破坏了条件,导致第二个线程继续执行 change(xxx) 时发生非预期结果(如内存访问越界、计算结果不对等)

5. 临界区

pthread_mutex_lock(&mutex);
do something...
pthread_mutex_unlock(&mutex);

其中 do something... 可以是一条语句,也可以是多条语句。 统称为临界区。

形象比喻: 马路上开车, 本来是双行道, 前方突然变为单行道,延续几百米后又变成双行道。 这段单行道就是临界区, 进入和离开临界区的 lock、unlock 行为, 可以认为是红绿灯。

6. 有了 atomic 变量就不用 mutex 了吗?

显然不对。 临界区的定义应当根据你实际执行的代码、实际的业务逻辑来判断。

7. 条件变量怎么用

通常是条件变量和一个普通变量绑定使用。

pthread_cond_signal() 和 pthread_cond_wait() 都是各自临界区的一部分, 也就是说不应该放到 pthread_mutex_lock() 和 pthread_mutex_unlock() 的外部。

pthread_cond_wait() 本身的文档明确指出: 它会执行释放mutex锁、执行等待,当条件被signal了就恢复,并且锁住 mutex, 因此不需要自行画手添足得给 cond_wait() 前增加 unlock 操作。

pthread_mutex_lock(&mutex);
  xxxxx
  pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);


pthread_mutex_lock(&mutex);
if ( false == testCondition )
    pthread_cond_wait(&cond, &mutex);
pthread_mutex_unlock(&mutex);

8. 封装

假设想要在 Windows 上也是用 pthread API, 不推荐用 MinGW 因为很多三方库没法编译链接,自己编译不现实。

pthreads-win32 是一个封装库, pthreads.h 里的常用 API 封装的还行。

ncnn 提供了 Windows 多线程和 Pthreads API 的封装, 整体风格是 pthreads 风格的: https://github.com/Tencent/ncnn/blob/master/src/platform.h.in

9. 其他高级主题

  • 设置线程优先级
  • 实现线程池
  • 各种花样的生产者消费者问题
posted @ 2024-05-03 23:57  ChrisZZ  阅读(292)  评论(0编辑  收藏  举报