linux下的线程,创建,等待,结束,分离

在传统操作系统中,进程是一个运行中程序中程序的描述信息–pcb控制程序运行.
linux下并没有为线程设计一个pcb来控制线程的运行
linux下的线程是以进程的pcb模拟的,进程里面也有pcb,就是线程,就是只有一个线程的进程并且这个线程运行起来是main函数称为主线程,linux下的pcb全是线程,—>linux下没有真正的线程因为linux下的线程是以进程 的pcb模拟的,所以linux下的线程也叫轻量级进程,linux每个pcb里面不仅仅有pid还有tgid(线程组id),每个pcb里面都会有一个tgid并且这些tgid是相同的,tgid=进程运行起来第一个线程的pid(第一个pcb的pid只是值等),ps —>看到的pid就是tgid线程组的id,所以linux下pcb是一个线程,进程变成了线程组.因为显示的id是进程组id,
线程组就是:进程,线程组的tgid就是线程组的id并且tgid等于主线程的pid
因为linux下线程以进程的pid模拟实现线程,因此linux下pcb是线程
因为linux下线程以进程pcb模拟实现进程因此linux线程也叫轻量级进程(轻量级指的是这个进程没有独立的虚拟地址空间多个pcb用了同一个虚拟地址空间,同一个页表占用的资源也更少更加轻量化所以叫轻量级线程)
1线程是进程内部的 执行流
2.一切进程至少有一个线程,线程在进程内部运行本质是:线程共用同一个虚拟地址空间在同一个虚拟空间上运行
3.在linux系统中,在cpu眼里看到的pcb比传统的进程更加轻量化(这些pcb相比较于传统的pcb共用同一个虚拟地址空间,占有的资源更少更加轻量化)
4透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流
因为linux下线程是pcb–因此线程是cpu调度的基本单位
因为进程是线程组–程序运行起来,资源是分配给整个线程组的,因此进程是资源分配的基本单位
多进程可以并行多任务,多线程也可以并行多任务哪个好?
一个进程中的线程共用同一个 虚拟地址空间(占用资源少,)
而且通信更加方便(定义一个全局变量大家都能看到 )(进程还需要 找一个公共的媒介)—>线程间通信更加方便
线程的创建/销毁成本更加低(资源创建释放更少)
线程之间的切换调度成本更低(同一个进程下的线程共用同一个虚拟地址空间同一个页表cpu调度的时候很多东西就不需要切换比如页表(映射在同一块))
线程的执行粒度更加细致(进程运行的程序线程运行的是一个程序中的一段代码,)
eg:抓流量分析写操作三步操作如果是同一个进程三个线程完成是直接可以处理每个模块负责各自的功能不需要太多的耦合性,通信依然方便,但是对于进程把连续的功能分成三个进程,通信方式比较麻烦,在爪到流量后交给下个进程下个进程分析处理完才能交给下个进程进行写
缺点:线程缺乏访问控制:–线程之间键壮性低,线程之间如果通过全局变量进行通信每个线程都要访问全局变量,中间没有访问控制,操作数据的时候就会造成数据的二义性(竞态条件)某些系统调用针对是进程产生的(exit退出进程,线程组,整个进程退出,那么线程无法存活,一个线程调用exit所有线程都会退出,退出整个进程),一个线程有内存访问错误,线程用的同一个页表段错误信号发送到整个进程退出的是整个进程---->一个线程出错全盘奔溃
多进程优点:–>健壮性高(独立)
eg;shell–>创建子进程执行指令,如果子进程奔溃了对shell没有影响,
多进程/多线程进行多任务处理的优势体现与细节:
cpu密集型程序:数据量大分多个进程和线程处理(不是无限制的–>资源不够–>一万个pcb切换调度也费时间并不是线程越多就效率越高,超过cpu负载反而降低(cpu要调度费时))
io密集型程序:
eg 10个文件多个线程可以同时读(读文件不是立即读还得等待),并行运行;
vfork创建一个子进程pcb共用一个虚拟地址空间,怕出现调用栈混乱,因此进程运行完毕或者程序替换后父进程才开始运行
多线程pcb同用一个虚拟地址空间:如何实现同时运行而不会出现调用栈混乱?
防止调用栈混乱每个线程在虚拟地址空间会单独分配一块空间:
虚拟地址空间中代码段数据段bss段未初始化数据段,初始化数据段,堆,共享区栈环境变量运行参数,线程地址空间在共享区,每个线程都会在共享区有个独立的线程地址空间
在这里插入图片描述
如图:
每个线程都会有一些独立的信息:
1>栈2>一套寄存器,上下文数据(上文(上一次进程处理的数据)正文(当前进程处理的数据)下文(即将要处理的数据))上下文数据包含了数据和指令
线程之间调度运行它们在cpu调度时(调度pcb)候会存在数据切换调度的问题也要将数据和代码保存起来在自己的pcb里面把数据保存到寄存器(独立的)
3>errno(也是私有的)错误码每个线程都会有一个
4>信号屏蔽字:每个线程都可以独立屏蔽自己想屏蔽的信号都有自己的block集合
5>调度优先级:独有
线程之间的共享的数据:
1>共享数据段和代码段
2>文件描述符表共享同一个,一个线程里面打开文件在另一个线程可以直接操作并且操作的是同一个文件,意味着一个进程里的多个线程同时从文件里面读数据会可能出问题,用的是同一个读写位置
3>每种信号的处理方式(SIG_IGN,SIG_DFL,或者自定义函数)
4>用户id组id,当前工作路径
线程控制:线程创建,线程终止,线程等待,线程分离
操作系统没有为用户提供直接线程的系统调用接口,但是被人封装了一套线程库实现线程控制因此有人称创建的线程是一个用户态线程,在内核中对应了一个轻量级进程实现程序的调度运行
int pthread_create(pthread_tthread,const pthread_attr_tattr,void*(start_routine)(void),void arg)
第一个参数是pthread_t类型的数据(无符号longint八个字节数据)输出型参数,用于获取创建线程的id(必须获取是操作句柄),第二个参数:const pthread_attr_t线程属性(通常置空,线程栈大小,溢出保护区大小栈大小继承特性几乎不涉及线程分离属性用单独的函数实现),void
(start_routine)(void)函数指针,线程运行就是一个函数,传入函数指针就是线程的入口函数,void arg最终传递到第三个参数viod,作为一个实参传递给它
头文件pthread.h
因为是库函数所以编译链接需要加上-pthread/lpthread链接线程库

gcc编译出现implicit declaration没有包含头文件

gcc creatpthread.c -o creatpthread
/tmp/ccdOCMLQ.o:在函数‘main’中:
creatpthread.c:(.text+0x54):对‘pthread_create’未定义的引用
collect2: 错误:ld 返回 1
make: *** [creatpthread] 错误 1
没有链接线程库
更改为:makefile+  ⮀ ⮂⮂ buffers
1 creatpthread:creatpthread.c
2 gcc $^ -o $ -pthread/-Lpthread
#include <unistd.h>
2 #include<errno.h>
3 #include<stdio.h>
4 #include<stdlib.h>
5 #include<pthread.h>
6 //int pthread_create(pthread_t thread,
7 //const pthread_attr_t attr,
8 //void (start_routine) (void ), void arg);
9 //thread:输出型参数,获取新创建的线程id
10 //attr:设置线程属性(一般置空)
11 //star_routine:线程入口函数
12 //arg:通过线程入口函数传递给线程的参数
13 //返回值:成功返回0,失败返回一个错误编号(errno>0)非0值
14 void
thr_start(void
arg){//线程入口函数
15 while(1){
16 printf(“i am a child–%s\n”,(char
)arg);//线程之间没有父子线程之说
17 //,最多有主线程和普通线程区别
18 sleep(1);
19 }
20 return NULL;
}
W> 22 int main(int argc ,char
argv[]){
23 pthread_t tid;//定义一个pthread_t类型的变量用于接收线程id
24 int ret=pthread_create(&tid,NULL,thr_start,(void
)“nihao”);
25 if(ret!=0){
26 printf(“thread create error\n”);
27 return -1;
28 }
29 while(1){
30 printf(“hello world\n”);
31 sleep(1);
32 }
33 return 0;
34 }
运行结果如下: [chenyongjie@bogon pthread]$ ./creatpthread
hello world
i am a child–nihao
hello world
i am a child–nihao
hello world
i am a child–nihao
将其tid打印出来
void
thr_start(voidarg){
7 while(1){
8 printf(“i am a child–%s\n”,(char
)arg);
9 sleep(1);
10 }
11 return NULL;
12 }
W> 13 int main(int argc ,charargv[]){
14 pthread_t tid;
15 int ret=pthread_create(&tid,NULL,thr_start,(void
)“nihao”);
16 if(ret!=0){
17 printf(“thread create error\n”);
18 return -1;
19 }
W> 20 printf(“child tid:%d\n”,tid);
21 while(1){
22 printf(“hello world\n”);
23 sleep(1);
}
25 return 0;}
结果为:
pthread]$ ./creatpthread1
child tid:901998336
hello world
i am a child–nihao
hello world
i am a child–nihao
tid:很大不是pid
//pthread_t _pthread_self(void);//获取线程id
7 //返回一个调用线程的id
8 void* thr_start(voidarg){
9 pthread_t tid=pthread_self();
10 while(1){
W> 11 printf(“i am a child–%s–%d\n”,(char
)arg,tid);
12 sleep(1);
13 }
14 return NULL;
15 }
运行结果为
thread]$ ./creatpthread1
child tid:-917457152
hello world
i am a child–nihao—917457152
hello world
i am a child–nihao—917457152
发现两个相同这个id,id没有出错,都是线程id一样的,但是不是tsak_struct里面的pid task_struct为int型通常很小但是这个数字很大
int main(int argc ,charargv[]){
17 pthread_t tid;//tid就是线程地址空间的首地址
18 int ret=pthread_create(&tid,NULL,thr_start,(void
)“nihao”);
19 if(ret!=0){
20 printf(“thread create error\n”);
21 return -1;
22 }
W> 23 printf(“child tid:%p\n”,tid);
24 while(1){
25 printf(“hello world\n”);
当还成%p发现是无符号长整型的数据,可以看出它是个地址
]$ ./creatpthread1
child tid:0x7f9f31a9a700
hello world
i am a child–nihao–833201920
i am a child–nihao–833201920
hello world
hello world
然后:
#include<pthread.h>
6 //pthread_t _pthread_self(void);//获取线程id
7 //返回一个调用线程的id
8 void* thr_start(voidarg){
9 pthread_t tid=pthread_self();
10 while(1){
W> 11 printf(“i am a child–%s–%d\n”,(char
)arg,tid);
12 sleep(1);
13 }
14 return NULL;
15 }
W> 16 int main(int argc ,charargv[]){
17 pthread_t tid;
18 int ret=pthread_create(&tid,NULL,thr_start,(void
)“nihao”);
19 if(ret!=0){
20 printf(“thread create error\n”);
return -1;
22 }
W> 23 printf(“main tid %p\n”,pthread_self());//获取自己的tid
W> 24 printf(“child tid:%p\n”,tid);
25 while(1){
26 printf(“hello world\n”);
27 sleep(1);
28 }
29 return 0;
30 }
运行结果为:
ead]$ make
gcc creatpthread1.c -o creatpthread1 -pthread
[chenyongjie@bogon pthread]$ ./creatpthread1
main tid 0x7f23f708c740
child tid:0x7f23f689a700
hello world
i am a child–nihao—158750976
hello world
然后打开监控:
ps -ef |grep creatptread1
在这里插入图片描述
会发现:自己线程id 0x7f23f708c740,和监控上的65525不是一个数字,所以获取的tid不是进程的pid
然后加上L选项
ps -efL |head -n 1&&ps -efL |grep creatptread1
在这里插入图片描述
PID进程的ID
ppid父进程ID
上面那个是主线程下面那个是线程
会发现lwp和pid一样都是6659然后底下的pid也是6659他们两个是同一个进程id但是会发现他的lwp不一样了是6660,
LWP:显示的是task_struct里面的pid,就是轻量级进程id也就是线程
NLWP显示的是当前进程里的线程个数,上图为两个线程
ps -L查看轻量级进程(线程)信息
pid起始显示的是线程组id
tid task_struct-pid task_struct-tgid
地址 LWP pid=主线程pid
线程
地址空间
的首地址
线程库的代码和数据都是加载到共享区的,一个线程在共享区开辟了一块自己的一块独立空间称为线程地址空间(包含了一些线程的结构信息,线程的局部存储和线程栈(struct pthread,线程局部存储,线程栈)),pthread_create创建一个线程它返回的就是线程地址空间(在共享内存区)在整个虚拟地址 空间的首地址,就是线程在共享区创建后就会返回一个首地址就是tid,通过tid获取到结构信息和调用栈
线程终止:

void* thr_start(void arg){
8 printf(“child thread----\n”);
9 return NULL;
10 }
W> 11 int main(int argc,char
argv[]){
12 pthread_t tid;
13 int ret=pthread_create(&tid,NULL,thr_start,NULL);
14 if(ret!=0){
15 printf(“thread create error\n”);
16 return -1;
17 }
18 while(1){
19 printf(“main pthread—\n”);
20 sleep(1);
21 }
22 return 0;
23 }
在这里插入图片描述
发现child只打印了一次一个线程的终止,return 是可以退出的
在main函数return 退出的是进程
1>在线程入口函数中return 就可以退出
void pthread_exit(void retval);void的返回值
2>调用pthread_exit,退出线程,retval作为返回值
void* thr_start(void *arg){
8 while(1){
9 printf(“child thread----\n”);
10 sleep(1);
11 //void pthread_exit(void retval)退出调用线程retval作为返回值
12 pthread_exit(NULL);
13 }
14 return NULL;
15 }
W> 16 int main(int argc,char
argv[]){
17 pthread_t tid;
18 int ret=pthread_create(&tid,NULL,thr_start,NULL);
19 if(ret!=0){
20 printf(“thread create error\n”);
21 return -1;
22 }
23 while(1){
24 printf(“main pthread—\n”);
25 sleep(1);
26 }return 0}

**pthread_exit返回值可以是NUll,*但是不是返回的数字,进程返回的是退出码判断进程任务运行如何,线程更加轻量化,所以线程的返回值不仅仅表示运行的怎么样,还 要返回运行结果(线程通常处理一段数据,完毕之后将处理结果返回)
如果将ptread_exit放到主函数会发现成了僵尸进程
void
thr_start(void *arg){
8 while(1){
9 printf(“child thread----\n”);
10 sleep(1);
11 //void pthread_exit(void retval)退出调用线程retval作为返回值
12 }
13 return NULL;
14 }
int main(int argc,char
argv[]){
16 pthread_t tid;
17 int ret=pthread_create(&tid,NULL,thr_start,NULL);
18 if(ret!=0){
19 printf(“thread create error\n”);
20 return -1;
21 }
22 while(1){
23 printf(“main pthread—\n”);
24 sleep(1);
25 pthread_exit(NULL);
26 }
27 return 0;
28 }
结果
thread]$ ./exitpthread
main pthread—
child thread----
child thread----
child thread----
在这里插入图片描述
[chenyongjie@localhost pthread]$ ps aux -L |grep exitpthread
chenyon+ 8487 8487 0.0 2 0.0 0 0 pts/0 Zl+ 12:12 0:00 [exitpthread]
chenyon+ 8487 8488 0.0 2 0.0 16836 656 pts/0 Sl+ 12:12 0:00 [exitpthread]
chenyon+ 8500 8500 0.0 1 0.0 112724 992 pts/1 S+ 12:12 0:00 grep --color=auto exitpthread
会发现主进程(主线程)退出了但是它的状态为Z但是其他线程任然运行为sl
所以线程退出也会形成僵尸线程,因为退出的是主线程所以把进程也体现为僵尸进程
!!!出现僵尸进程:1>子进程退出2>线程中退出主线程
主线程退出但是进程不一定退出(和进程退出没有关系pthread_exit功能只是退出线程而不是进程)
但是将主程序的pthread_exit删除放到线程里面会发现:
普通线程不体现僵尸线程–>线程并不体现僵尸线程,僵尸线程不会体现出来,刚体现出来的僵尸线程是因为是主线程ps-aux是以主线程信息为主,所以显示出来的是僵尸状态
pthread_exit退出调用线程
主线程退出,进程不会退出,线程退出也会成为僵尸线程(但是普通线程体现不出效果–>因为它也有返回值,而且也占内存),线程地址空间无法回收在利用造成内存泄漏—>如何处理?–>线程等待:

void* thr_start(void arg){
8 while(1){
9 printf(“child thread----\n”);
10 sleep(1);
11
12 }
13 return NULL;
14 }
W> 15 int main(int argc,char
argv[]){
16 pthread_t tid;
17 int ret=pthread_create(&tid,NULL,thr_start,NULL);
18 if(ret!=0){
19 printf(“thread create error\n”);
20 return -1;
21 }
22 pthread_cancel(tid);
23 //int pthread_cancel(pthread_t thread);一个参数tid,取消一个指定
24 //线程只要给定线程tid就可以了tid==thread
25 while(1){
printf(“main pthread—\n”);
27 sleep(1);
28 }
29 return 0;
30 }
在这里插入图片描述

在这里插入图片描述
发现普通线程取消了
child只打印了
pthread_cancel(tid);---->换成pthread_cancel(pthread_self());//取消自己
发现也可以但是不合规
线程终止:
return null
pthread_exit 退出调用线程
pthread_cancle 取消一个指定的线程
线程等待:获取指定线程的返回值,允许系统回收资源;
因为一个线程运行起来默认有一个属性:joinable这个属性决定了线程退出后,必须被等待,否则线程资源无法完全释放,成为僵尸线程(这个僵尸线程无法直观体现),这个joinable会保存退出返回值,不会自动退出,资源也就不会释放
线程等待有一个前提:线程能够被等待
pthread_join
void* thr_start(void arg){
11 sleep(3);
E> 12 return “nihao”;//返回字符串的首地址
13 }
W> 14 int main(int argc,char
argv[]){
15 pthread_t tid;
16 int ret=pthread_create(&tid,NULL,thr_start,NULL);
17 if(ret!=0){
18 printf(“thread cread errn\n”);
19 }
20 charptr;
21 pthread_join(tid,(void
)ptr);
22 printf(“返回值:%s\n”,ptr);
23 }
在这里插入图片描述
eg:
test (int b){
b=100;}
int a=0;
调用test(a)然后打印结果为printf(a)----0
传值进去没有生效
test(b){
b=100;}
int a=0;
调用test(&a)然后打印printf(a)–100
test(char
a){
a=“test”}
char
a=null;
然后调用test(a)则printf(a)—>null
还是串值
想要获取这个数据需要一个二级指针
test(chara){
*a=“test”}//*a解引用为空没有空间所以打印出来还是空
char
a=null;
printf(a)–>null
a变量是char
*(为两个**)类型,一个二级指针八个字节空间里面保存了一个地址,然后解引用(a)–>就是它这个空间所指向地址的空间,相当于地址还有空间但是char(**)为空所以就没有空间,所以a变量才有空间(a)没有空间,所以修改(a)的值就出错了没有空间
所以为了获取一个nihao指针,如果用char
ptr=null;并不会对其进行修改相当于传值进去了所以对void**取地址修改它自身
定义了一个变量a在函数里面修改它int
a–>&a
int a,修改它也是&a,test(inta)
int a 修改它是&a test(int
a)
修改什么变量就要对它取地址
#include<errno.h>
7 //int pthread_join(pthread_t thread,void
*retval)
8 //第一个参数要等待的线程id第二个参数retval:输出型参数用于获取退出
9 //线程的返回值
W> 10 void
thr_start(void arg){
11 sleep(3);
E> 12 return “nihao—”;
13 }
W> 14 int main(int argc,char
argv[]){
15 pthread_t tid;
16 int ret=pthread_create(&tid,NULL,thr_start,NULL);
17 if(ret!=0){
18 printf(“thread cread errn\n”);
19 return -1;
20 }
21 charptr=NULL;
22 pthread_join(tid,(void**)&ptr);//取地址修改自身
23 printf(“返回值:%s\n”,ptr);
24 }
在这里插入图片描述
发现正确
//线程的返回值
W> 10 void
thr_start(void arg){
11 sleep(3);
12 char buf[]=“woshi9527”;
W> 13 return buf;
14 }
W> 15 int main(int argc,char
argv[]){
16 pthread_t tid;
17 int ret=pthread_create(&tid,NULL,thr_start,NULL);
18 if(ret!=0){
19 printf(“thread cread errn\n”);
20 return -1;
21 }
22 charptr=NULL;
23 pthread_join(tid,(void**)&ptr);//取地址修改自身
24 printf(“返回值:%s\n”,ptr);
25 }
发现结果不对,函数返回的是局部变量的地址,函数结束之后就释放了,函数一旦退出就释放了所以获取不到
在这里插入图片描述
但是直接返回(return “nihao”)却可以是因为你好是常量在代码段;所以不会被释放掉
void
thr_start(void arg){
11 sleep(3);
12 char buf[]=“woshi9527”;
E> 13 return “nihao”;
14 }
W> 15 int main(int argc,char
argv[]){
16 pthread_t tid;
17 int ret=pthread_create(&tid,NULL,thr_start,NULL);
18 if(ret!=0){
19 printf(“thread cread errn\n”);
20 return -1;
21 }
22 pthread_cancel(tid);//异常退出不具备评判标准,返回值没有任何价值
23 char*ptr=NULL;
24 pthread_join(tid,(void**)&ptr);//取地址修改自身
E> 25 printf(“返回值:%d\n”,(int)ptr);
26 }
此处 线程没有运行完就退出不做为评判标准返回值没右价值
在这里插入图片描述
如果一个线程被取消,则返回值为一个宏(PTHREAD_CANCELED)为-1
线程分离:将线程的属性从joinable设置为detach属性
处于detach属性的线程,退出后资源直接自动被回收这类线程不能被等待

用法:通常如果用户对线程的返回值并不关心,则在创建线程之后直接分离线程或者在线程 入口函数中第一时间分离自己
int pthread_detach(pthread_t thread);
线程tid
方法:1自己分离自己(在自己的线程里面)

法2线程创建后分离
EINVAL thread is not a joinable thread.
不是joinable属性
#include<stdlib.h>
6 #include<errno.h>
7 //int pthread_detach(pthread_t thread);
8 //线程分离,一般在线程创建出来就对线程进行分离,在线程里面调用
9 //线程的分离对于一个线程来说任意线程在任意位置调用都可以
W> 10 void* thr_start(void arg){
11 // pthread_detach(pthread_self());//自己分离自己
12 sleep(3);
13
E> 14 return “nihao”;
15 }
W> 16 int main(int argc,char
argv[]){
17 pthread_t tid;
18 int ret=pthread_create(&tid,NULL,thr_start,NULL);
19 if(ret!=0){
20 printf(“thread cread errn\n”);
21 return -1;
22 }
23 pthread_detach(tid);
24 char*ptr=NULL;
int ret1=pthread_join(tid,(void**)&ptr);//取地址修改自身
26 if(ret1==EINVAL){
27 printf(“this thread can not be wait\n”);
28 }
29 // printf(“返回值:%d—%d\n”,ret1,(int)ptr);
30 return 0;
31 }
在这里插入图片描述
如图所示

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值