本文由线程的创建和退出、Linux中man操作手册用法、线程的堆和栈加之自己的理解整合而来。
一、创建线程
1.1 调用接口
自问:1为什么不直接使用整型变量记录线程id呢,就和pid一样不是更好吗?2restrict关键字是什么意思?
先回答1,linux的发展历史UNIX操作系统有很多版本,Linux 2.4.22使用无符号长整型数表示pthread_t类型。Solaris把pthread_t数据类型表示为无符号整型数。FreeBSD和Mac OS X 10.3用一个指向pthread结构的指针来表示pthread_t数据类型。种类繁多,没有统一的规范,这就给代码的可移植性带来了挑战。用结构pthread_t数据类型的后果是不能用一种可移植的方式打印该数据类型的值。在程序调试过程中打印线程ID有时候是非常有用的。我觉得,这就和大小端模式一样无聊,虽然今天的局面是历史原因,但为什么不在当时开个会讨论一套标准出来不是更好吗?也许这就是计算机世界的魅力吧!
再回答2,关键字restrict是什么意思?
通过翻译,我们看到了,它的意思是限定,这是英语告诉我们的;但是来到计算机世界,这个关键字到底限定什么呢? restrict关键字,表明指针所指向的内容,不能通过除此指针之外的其他直接或间接的方式修改。这就表明内容只唯一允许此指针修改,本要求是很强的了。
还有一点特别坑的地方就是——在创建函数返回之前新线程可能已经开始运行了。创建未完成,新线程已开始运行,这也是没谁了。
总结一下就是:
- 线程标识符是pthread_t类型的
- 关键字restrict在pthread_create中被使用
- 在创建函数返回之前新的线程可能已经开始运行了
1.2 打印输出线程tid
想打印输出线程tid会比进程pid麻烦的多。如果进程创建函数的特性是返回之后,函数线程才开始执行,那么我们就可以从形参tidp中把线程的信息取出来,经过分析得到tid。事实却不是这样,如果我们非要这么做的话,新线程看到的可能是初始世化的形参,这个内容是不正确的线程tid。
介绍一个新的API,pthread_self(),功能是返回调用该函数的线程的id。在此不过多赘述实现思路,直接看实现代码吧。
1.3 代码实现
头文件的引入以及自主实现函数的声明:
#include<stdio.h> //printf()
#include<unistd.h> //getpid()
#include<pthread.h> //pthread_create() pthread_self()
#include<stdlib.h> //exit()
#include<assert.h> //assert()
void print_ids(const char *buff); //打印输出线程id
void* thr_fn(void*arg); //线程
main主线程
int main()
{
pthread_t tid;
int err = pthread_create(&tid, NULL, thr_fn, NULL);
assert(0 == err);
print_ids("main");
sleep(2); //确保主线程在函数线程结束后结束
exit(0);
}
其他
void print_ids(const char *buff) //打印输出线程信息
{
pid_t pid = getpid(); //获得进程编号
pthread_t tid = pthread_self(); //获得线程编号
printf("%6s : pid = %u, tid = %u (0x%x)\n", buff, pid, tid, tid);
}
void* thr_fn(void*arg) //函数线程
{
print_ids("thr_fn");
return NULL;
}
1.4 运行结果
小实验是完成了,但是还是有问题问自己, 从实验结果中观察到了什么?看图说话:①main主线程和thr_fn函数线程对应的pid相同——这是很自然的,因为它们是同一进程的不同线程,进程id肯定相同;②main主线程和thr_fn函数线程对应的tid不同,反复强调一点:tid的类型是pthread_t,到底是否是整型是未知的。
二、man操作手册
2.1 括号内数字的含义
我的建议是遇见了不熟悉的Linux API,不要百度或谷歌,直接查看Linux操作手册就可以了,最权威,最全面,能在最短的时间提高自己。
2.2 API-pthread_seft
可移植
上图是我从release 3.22 of the Linux 截取出来的,一共分四段话。第一段大意是说表述描述一个线程标识符的数据类型是自由的,这就导致使用C语言中的==判断是否相等变得不可移植;第二段大意是说线程标识符是不透明的,离开进程的大环境,一个线程并不能被识别;第三段大意是说线程标识符仅仅保证在进程内是独一无二的,而且可以被重复利用;第四段大意是说pthread_self返回的thread ID和pthread_self返回的kernel thread ID不是用一个事物。我们还是要进一步了解一下gettid()是什么。
2.3 API-gettid()
不可移植 Linux特有
上图是我从release 3.22 of the Linux 截取出来的,主要告诉读者:gettid() 是Linux特有的,不应该将其用在可移植的程序上,直接返回调用者线程id,这次真的是一个整型了。在描述中,又出现黑体字强调的clone() 函数,有必要去刨根问底了解一下。
2.4 API-clone()
Linux实现创建线程最终转调的函数,类似于fork(),注意比较两者的差别。
上图是我从release 3.22 of the Linux 截取出来的,文字很长,但是表达的意思很清楚。首先,clone函数是一个库函数,用于创建一个子进程,但是创建出来的子进程会和父进程共享很多资源,例如内存、文件描述符表、信号处理器表。因为内存是共享的,所以子进程必须要有自己独立的栈,child_stack就是做这个事情的。还需要有cb,函数指针+参数列表:fn和arg做这个事情的。flag主要是通过位运算控制共享资源的内容的。此处所指代的子进程就是我们理解的线程。