本文的环境:
Linux centos-7.shared 3.10.0-693.5.2.el7.x86_64 #1 SMP Fri Oct 20 20:32:50 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
gcc 版本 4.8.5 20150623 (Red Hat 4.8.5-39) (GCC)

本文使用 POSIX 线程库,需引入头文件 pthread.h,在编译的时候要注意添加 -lpthread 参数。

POSIX 是一个标准,约定一个操作系统应该提供哪些接口,pthread 即为 posix thread。C++11、Python、Java 均内置了线程库。

线程创建

1
2
3
4
5
6
int pthread_create(
pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine) (void *),
void *arg
);

参数:
thread 是一个输出型参数,返回一个线程 id。
attr 可以设置线程属性,填 NULL 表示使用默认属性。
start_routine 是一个函数指针,是线程执行的入口函数。
arg 是 start_routine 的函数参数。
值得注意的是这个函数 arg 是不支持传递多个参数的(可变参数),如果需要传递多个函数就需要使用 struct 或者一些别的方式。

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

栗子:
创建一个线程,传递一个参数,并在这个新线程内打印这个参数。
在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

struct ThreadArg {
int num;
};

void* ThreadEntry(void* arg) {
while (1) {
printf("In ThreadEntry, %lu, arg %d\n", pthread_self(), ((struct ThreadArg*) arg)->num);
sleep(1);
}
}

int main() {
pthread_t tid;
struct ThreadArg ta;
ta.num = 20;
pthread_create(&tid, NULL, ThreadEntry, &ta);
while (1) {
printf("In Main Thread, %lu\n", pthread_self());
sleep(1);
}

return 0;
}

==注意:==
上面代码中使用了 pthread_self() 获取线程的 id,这是 POSIX 线程库提供的库函数,操作系统也提供了一个获取线程 id 的系统调用 gettid()

1
2
pthread_t pthread_self(void);
pid_t gettid(void);

但是当你在同一个线程调用两个函数的时候发现返回的并不是相同的值。

The thread ID returned by this call is not the same thing as a POSIX thread ID.
(i.e., the opaque value returned by pthread_self(3)).
对于单线程的进程,内核中tid==pid,对于多线程进程,他们有相同的pid,不同的tid。tid用于描述内核真实的pid和tid信息。
pthread_self返回的是posix定义的线程ID,man手册明确说明了和内核线程tid不同。它只是用来区分某个进程中不同的线程,当一个线程退出后,新创建的线程可以复用原来的id。

gettid() 和 pthread_self()的区别

线程终止

想让一个线程结束而不终止进程:
1、从线程处理函数 return
2、线程自己调用 pthread_exit 切腹自尽。

1
void pthread_exit(void *retval);

参数:输入 & 输出型参数。
==注意==:pthread_exit 参数所指向的内存单元必须是全局的或者是用malloc 分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。

3、兄弟线程调用 pthread_cancel 终止同一进程中的另一个线程。

1
int pthread_cancel(pthread_t thread);

参数:线程 id
返回值:成功返回 0,失败返回错误码。
==注意==:这个 pthread_cancel 是温和的终止一个线程而不是强制 kill 掉,抽象一个例子就是你在召唤师峡谷杀敌ing,你妈喊你吃饭,你可能得等一会才过去吃饭。

线程等待

为什么要线程等待?
例如,计算一个很大的矩阵相乘,可以使用多线程方式来计算,每个线程计算其中的一部分,最终等待所有的线程执行完,主线程汇总结果,这里就用 pthread_join 来保证逻辑。

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

参数:thread 线程 id,retval 指向一个指针,这个指针指向线程的返回值,不关注线程的返回值可以填 NULL。
返回值:成功返回 0,失败返回错误码。
调用该函数的线程将挂起等待,直到 id 为 thread 的线程终止。thread 线程以不同的方法终止,通过 pthread_join 得到 的终止状态是不同的:
1、如果thread线程通过 return 返回, value_ ptr 所指向的单元里存放的是thread线程函数的返回值。
2、如果thread线程被别的线程调用 pthread_cancel 异常终掉, value_ ptr 所指向的单元里存放的是常数 PTHREAD_CANCELED。
3、如果thread线程是自己调用 pthread_exit 终止的,value_ptr 所指向的单元存放的是传给 pthread_exit 的参数。

线程分离

类似于忽略 SIGCHLD 信号,分离后就代表线程的死活不管了,也就不用 pthread_join 回收了。

1
int pthread_detach(pthread_t thread);

可以自己把自己分离出去,也可以被兄弟线程分离。
pthread_detach(pthread_self());

使用 gdb 调试多线程程序

0、使用 gdb 调试多线程程序

使用 gdb 调试一个程序需要在编译时候加上 ==-g== 选项,为什么?百度去。

1
gdb attach 28966
1
2
3
info thread # 查看所有线程信息
bt # 查看当前线程调用栈
thread 2 # 切换当前线程

在这里插入图片描述

1、查看一个程序的所有线程信息

1
ps -eLf | grep a.out

参数:-L 表示 LWP,这里 ps 得到的线程 id 是和 gettid 一样的。
在这里插入图片描述

2、查看一个程序依赖哪些库

1
ldd a.out

在这里插入图片描述
3、查看一个进程中有几个线程和线程调用栈

1
pstack 27779

在这里插入图片描述


EOF