1,前言
一个进程内可以有多个线程,这些线程可以访问进程的文件描述符、内存。每个线程都有自己的ID,线程ID。线程ID只有在它所属的进程上下午中才有意义。
每个线程含有如下信息:
- 线程ID;
- 寄存器值;
- 栈;
- 调度优先级;
- 信号屏蔽字
- errno
- 线程私有数据
一个进程的所有信息都对该进程的所有线程共享,包括:
- 可执行程序的代码;
- 全局内存;
- 堆内存;
- 栈;???
- 文件描述符
关于进程栈和线程栈总结:
- 进程栈大小时执行时确定的,与编译链接无关
- 进程栈大小是随机确认的,至少比线程栈要大,但不会超过2倍
- 线程栈是固定大小的,可以使用ulimit -a 查看,使用ulimit -s 修改
- 一般默认情况下,线程栈是在进程的堆中分配栈空间,每个线程拥有独立的栈空间,为了避免线程之间的栈空间踩踏,线程栈之间还会有以小块guardsize用来隔离保护各自的栈空间,一旦另一个线程踏入到这个隔离区,就会引发段错误。
2,线程ID
线程ID的类型是pthread_t,进程ID类型是pid_t.
pthread_t通常被实现为无符号长整形,但也可能是个结构体。所以判断两个线程ID是否相等,不能直接用等号,必须使用函数:
#include <pthread.h>
int pthread_equal(pthread tid1, pthread tid2);
相等返回非0值,不相等返回0.
获取当前线程的线程ID,则使用下面的函数:
#include<pthread.h>
pthread_t pthread_self(void);
3,创建线程
在讲进程的时候我们提到过,可以通过fork和execl把一个可执行文件扔到一个新的进程里去执行。现在,我们想要把某个函数扔到一个新线程里去执行。它需要调用下面的函数:
#include <pthread.h>
int pthread_create(pthread_t *restrict tidp,
const pthread_attr_t *restrict attr,
void *(*start_rtn)(void*),
void *restrict arg);
其中,
- tidp代表这次创建的新线程的ID;
- attr代表新线程的属性,暂时不管;
- start_rtn是我们要扔到新线程里去执行的函数,注意,这个函数的类型被定死了,输入参数是void*,返回类型也是void*;
- arg是start_rtn的输入参数,如果我们事实上呀传递的参数有多个,那就把这多个参数放到一个结构体里,再把结构体的地址传进来;
4,一个简单的例子
我们写一个简单的例子,它创建新线程,并打印主线程和新线程的ID。
代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
pthread_t ntid; //新创建线程的线程ID,线程可以使用进程的全局变量
void printids(const char* s); //打印当前进程、线程ID
void* thr_fn(void *arg); //要在线程里执行的函数,输入参数类型和返回类型都是void *
int main(){
int err;
//创建新线程,执行thr_fn函数,线程ID存进ntid
err = pthread_create(&ntid, NULL, thr_fn, NULL);
if (err)
err_sys("pthread_creat error!");
//打印主线程的ID
printids("main thread");
//等待1秒,避免新线程运行前进程就退出了
sleep(1);
exit(0);
}
//打印当前进程、线程ID
void printids(const char* s)
{
pid_t pid; //进程ID
pthread_t tid; //线程ID
pid = getpid();
tid = pthread_self();
printf("%s: pid %lu, tid %lu (0x%lx)\n", s, (unsigned long)pid, (unsigned long)tid, (unsigned long)tid);
}
//要在线程里执行的函数
void* thr_fn(void *arg)
{
printids("new thread");
return((void *)0);
}
代码很简单,但是有两点需要注意:
- 我们打印新线程的线程ID,不是在主线程里打印全局变量ntid,而是在新线程里主动获取了ID,为什么要这样呢?因为我们不能保证在调用pthread_creat()后,是新线程先执行,还是主线程先执行。如果在pthread_creat返回后,新线程尚未执行,那么此时全局变量ntid就是未赋值的值。所以安全的做法还是在新线程里获取自己的ID;
- 这里主线程休眠了一秒钟。为什么呢?原因同上。如果主线程先执行,可能主线程已经返回了,进程结束了,新线程还没执行呢。所以然主线程休息下,等新线程。
- 如果想在新线程里使用ntid(虽然我们这里没用),就必须把他定义成全局变量。因为线程是可以访问进程的全局存储区的。但是,新线程访问不了主线程的栈。所以如果其定义成main函数里的局部变脸,新线程是访问不到的;
这段代码再编译的时候有一点需要注意:如果使用常用的编译指令
g++ -g -W -o study_Linux study_Linux.c
那么编译会报错:
➜ code gcc -g -W -o study_Linux study_Linux.c
/tmp/cc4sSenu.o: In function `main':
/home/huangyang/code/study_Linux.c:423: undefined reference to `pthread_create'
collect2: error: ld returned 1 exit status
为什么呢?因为pthread的实现并不是在c/c++的标准库里。我们如果想用pthread,就必须自己加上对应的库。所以正确的编译指令是:
g++ -g -W -o study_Linux study_Linux.c -lpthread
-lpthread 代表pthread的库。
执行后的结果如下:
➜ code ./study_Linux
main thread: pid 140, tid 140676846913344 (0x7ff1e17d0740)
new thread: pid 140, tid 140676836427520 (0x7ff1e0dd0700)
可见,主线程和新线程,进程pid相同,线程pid不同。这个线程pid可能为线程结构的内存地址。