文章目录
1. 目的
Pthread 提供了 Linux / MacOSX 等系统下的一套多线程 API。使用多线程可以用于提升 CPU 利用率, 加速任务执行等。
1.1 不使用 Pthread 的情况
如果要使用多线程, 但是又不使用 Pthread, 我只能想到如下几种情况:
- 限定在 Windows 操作系统上,使用 Windows 的那一套多线程 API
- 使用
std::thread
, 坚定的 modern C++, pure C++ 信仰 - 有一套封装好了的 API, 能支持当前系统, 例如封装了 Pthread 和 Windows thread, 甚至封装了
std::thread
1.2 使用 Pthread 的情况
实际上 Pthread 在非常多的 操作系统
x 编译器
x 硬件平台
组合条件下可以使用:
- Linux x64 环境: 例如服务端编程,搞Web后端网络开发的
- 深度学习加速,当然可能 openmp 也基本够用
- Android 和 TDA4 这样的嵌入式设备上,对应到手机、车载两大消费者平台
1.3 使用 Pthread 的好处
使用 Pthread 还有额外的好处:
- 能使用
std::thread
, 大概率也能使用 Pthread,Windows 上有 Windows-Pthreads 库可以使用 - Pthread 是 C 接口, 纯 C 环境仍然可以使用
- Pthread 的例子很多, 开源代码也很多,甚至可能比 std::thread 的资料和例子多不止一倍
以上,是我学习使用 Pthread 的主要原因。
2. Pthread 创建线程的 API
2.1 环境
使用 ubuntu 22.04。它自带的 man 可以查询 pthread 的 API。
2.2 pthread_create()
man pthread_create
PTHREAD_CREATE(3) Linux Programmer's Manual PTHREAD_CREATE(3)
NAME
pthread_create - create a new thread
SYNOPSIS
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
Compile and link with -pthread.
2.3pthread_join()
这个API用于在当前线程等待子线程的结束。
PTHREAD_JOIN(3) Linux Programmer's Manual PTHREAD_JOIN(3)
NAME
pthread_join - join with a terminated thread
SYNOPSIS
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
Compile and link with -pthread.
3. 创建最简单的线程
3.1 要点
何为最简单?能省则省, 能用默认就用默认。换言之使用最小必要条件创建一个子线程。
要点:
- 子线程需要执行一个函数,需要实现这个函数
f
(命名就很简洁) - 函数
f
需要返回void*
类型 - 函数
f
需要传入void*
类型参数 - 创建线程, 需要调用
pthread_create()
函数 - 主线程需要等待线程结束,调用
pthread_join()
函数 - 只创建一个子线程
3.2 代码
按如上要点很容易写出如下实现:
simplest.cpp
//
// 最简单的 pthread 创建线程的例子: 创建一个线程。线程参数和线程属性都用NULL
//
#include <pthread.h>
#include <stdio.h>
// 1. 线程函数返回值, 必须是 void*
// 2. 线程函数参数, 必须只有一个, 类型必须是 void*
void* f(void*)
{
printf("This is thread function\n");
return NULL;
}
int main()
{
pthread_t t; // 3. 创建线程对象变量
pthread_create(&t, NULL, f, NULL); // 4. 创建线程: 传入线程对象(参数1), 传入线程函数(参数3)。
pthread_join(t, NULL); // 等待线程结束
return 0;
}
编译并运行:
zz@Legion-R7000P% clang++ simplest.cpp
zz@Legion-R7000P% ./a.out
This is thread function
可以看到, 函数 f
被执行, 而 f
并不是按常规的 f()
方式显式调用,而是一种回调(callback)方式调用的。
4. 创建多个子线程
4.1 要点
基于前一节的代码,这次创建4个线程。创建线程、等待子线程结束的两段代码,各自用 for 循环包裹起来即可实现。
4.2 代码
multiple_threads.cpp
:
//
// 创建多个线程。线程属性和线程参数仍然用NULL。
//
#include <pthread.h>
#include <stdio.h>
void* hello(void*)
{
printf("This is hello\n");
return NULL;
}
int main()
{
const int N = 4;
pthread_t t[N];
for (int i = 0; i < N; i++)
{
pthread_create(&t[i], NULL, hello, NULL);
}
for (int i = 0; i < N; i++)
{
pthread_join(t[i], NULL);
}
return 0;
}
编译并运行:
zz@Legion-R7000P% clang++ multiple_threads.cpp
zz@Legion-R7000P% ./a.out
This is hello
This is hello
This is hello
This is hello
可以看到, 输出了4句相同的 This is hello
, 说明确实运行了4次 f
函数, 而且主线程并没有显式调用 f()
, 而是一种回调方式, 通过子线程来实现的调用。
5. 总结
这里的例子很简单, 说是简陋也不为过:
- 参数是 NULL,没有传递有效参数
- 各个线程之间独立, 没有展示互相依赖的情况
- 主线程等待子线程结束, 没有考虑不需要等待的情况