java的多线程,linux下的多线程

 2023-09-25 阅读 18 评论 0

摘要:多线程:包括线程概念/线程控制/线程安全/线程基本应用 java的多线程、多任务处理:多创建几个进程,一个进程就有一个pcb,能够串行化的完成一个任务,在一个进程中多创建几个pcb,pcb是调度程序运行的描述,有多少个pcb就有多少个

多线程:包括线程概念/线程控制/线程安全/线程基本应用

java的多线程、多任务处理:多创建几个进程,一个进程就有一个pcb,能够串行化的完成一个任务,在一个进程中多创建几个pcb,pcb是调度程序运行的描述,有多少个pcb就有多少个执行流程,

什么是线程?

redis多线程,线程是一个进程内部的控制序列,线程是进程中的一条执行流,在linux下通过pcb实现,因此实际上linux下的线程就是一个pcb,并且linux下的pcb共用一个虚拟地址空间,相较于传统pcb更加轻量化,也被称之为轻量级进程。

shell 多线程。

 多线程是同时处理,只有一个虚拟地址空间,多进程各有各的虚拟地址空间和执行代码段。

进程是操作系统资源分配的基本单位;(操作系统会为一个程序的运行分配所需的所有资源)

线程是cpu调度的基本单位。

linux下的进程其实是一个线程组,一个进程中可以有多个线程(多个pcb),线程是进程中的一条执行流。一个进程中至少会有一个线程。

创建线程会伴随在内核中创建一个pcb来实现程序的调度,作为进程中的一条执行流。

进程就是多个线程的一个合集,并且这个进程中的所有pcb共用进程中的大部分资源(程序运行时,操作系统为程序运行所分配的所有资源)。

线程之间的独有与共享:

独有:标识符,寄存器,信号屏蔽字,优先级。

共享:虚拟地址空间(代码段/数据段),文件描述符表(IO信息),信号处理的回调函数(SIG_IGN忽略、SIG_DFL默认或自定义),当前工作目录,用户id和组id。

为什么信号是先注销,再处理?

信号时针对整个进程通知事件进行处理的,但是一个信号只需要被处理一次就够了,然而一个进程会有多个执行流,到底谁处理这个事件(谁拿到时间片,谁就能处理),有的线程不希望操作被打断,就可以屏蔽这个信号。

使用vfork创建一个子进程;父子进程共用同一个虚拟地址空间,但是父进程会阻塞(父进程阻塞的原因是防止调用栈紊乱,数据紊乱)直到子进程exit或者程序替换;

fork(写时拷贝,修改的时候开辟空间)不仅会创建pcb还会为pcb创建虚拟地址空间,创建页表,因此fork后父子进程数据独有(各有各的虚拟地址空间,若数据发生改变映射后指向不同的物理内存区域),代码共享的。

fork完虚拟地址空间是立即创建的,物理内存没有立即创建,而是数据发生改变时(是改变页表映射)才会重新开辟,拷贝数据过来。

创建线程是创建一个pcb,然后通过指针指向同一个虚拟地址空间,映射的也是同一块物理内存。

多线程:多个pcb作为独立执行流肯定是同时运行,如何做到不会调用栈混乱

errno:系统调用完毕后重置的一个全局变量。

多线程/多进程进行多任务处理的优缺点分析:

多线程的优点:

1.线程间通信更加灵活方便(除了进程间通信方式之外还有全局变量以及函数传参)--共用同一个虚拟地址空间,只要知道地址就能访问同一块空间)

2.线程的创建与销毁成本更低(创建线程创建一个pcb,共用的数据只需要使用一个指针指向同一处就可以)

3.同一个进程中的线程间调度成本更低(调度切换需要切换页表...)

多进程的优点:

1.多进程的健壮性,稳定性更高(异常以及一些系统调用exit直接针对整个进程生效)

共同的优点:等待+数据拷贝

1.IO密集型程序多任务并行处理,对cpu要求并不高,执行流个数没有要求(多磁盘可以实现同时处理)

2.CPU密集型程序:程序中进行大量数据运算处理;cpu资源足够,就可以同时处理,提高效率                                    (通常执行流的个数是cpu核心数+1)

创建线程很多的话,而cpu资源不够多,会造成大量的进程切换调度成本提高

线程控制:通过代码实现线程的创建/退出/等待/分离

线程控制的代码都是库函数,有封装的线程库函数。使用库函数创建一个线程,本质是在内核中创建一个轻量级进程实现程序调度。

线程创建-创建一个新的线程:

   int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *
(*start_routine)(void*), void *arg);

    thread:输出型参数,用于获取线程ID--线程的操作句柄
    attr:设置线程的属性,attr为NULL表示使用默认属性
    start_routine:是个函数指针,线程启动后要执行的函数,运行完毕,线程退出
    arg: 传给线程启动函数的参数
    返回值:成功返回0;失败返回错误码(非0)

声明和定义的区别就是有无实现。

未声明:不知道这个函数或变量,从来没有。(通常是没有添加头文件)

未定义:只是不知道这个函数或变量的空间和实现在哪里。(没有把库添加上)

线程创建代码如下:

 tid是一个无符号长整形数据,一个线程就有一个pcb,每个pcb都有一个pid--pid是一个整形数据。

tid和pid有什么联系?

pid--是一个轻量级进程id,是内核中task_struct结构体中的id。

 tid---是一个线程id,线程的操作句柄,这个tid是用户态线程的id是线程独有的这块空间的首地址,每个线程被创建出来之后,都会开辟一块空间,存储自己的栈,自己的描述信息。

ps -ef|grep create 看到的进程信息中的pid到底是哪个pcb的pid?

 task_struct-->pid:轻量级进程id,也就是ps -ef看到的LWP

 task_struct-->tgid:线程组id,等于主线程id (也就是外边所看到的进程id)

LWP和tgid实际上几乎用不到,因为线程的操作都是通过tid完成的。

 结果如下:

 库的链接使用:

whereis libpthread.so可以查看库的位置。

1.将库文件放在指定的路径下 /lib64    /usr/lib64

2.将库文件路径添加到环境变量  LIBRARY_PATH中。

3.使用gcc -L选项指定库文件所在路径。

4.gcc默认链接动态库,动态库也可以使用-L指定库路径,-l指定库名称

线程终止--线程退出--如何退出一个线程?

线程入口函数运行完毕,线程就会自动退出--在线程入口函数中调用return(但main函数中return退出的是进程而不是主线程)

 

结果可以看到普通线程只运行了一次 就退出,而主线程没有退出。

void pthread_exit(void *retval); 退出线程接口--谁调用谁退出,retval是退出返回值。

但是exit无论在哪都是进程退出。
代码如下:

 结果如下:

发现主线程退出了,普通线程还依然在打印。通过查看进程发现,有2个exit,第一个为僵尸状态-僵尸进程,第二个为休眠状态。

 

 主线程退出,并不会导致进程退出,只有所有的线程都退出了,进程才会退出。

 int pthread_cancel(pthread_t thread);取消一个线程;退出的线程被动取消的。

线程等待:等待一个线程的退出,获取退出线程的返回值,回收线程所占的资源

线程有一个属性,默认创建出来这个属性是joinable,处于这个属性的线程,退出后,需要被其他线程等待获取返回值回收资源。

 int pthread_join(pthread_t thread, void **retval);--等待指定线程退出,获取其返回值

thread:要等待退出的线程tid

retval:输出型参数,用于返回线程的返回值。
线程的返回值是一个void*,是一个一级指针。若要通过一个函数的参数获取一级指针,就需要传入一个一级指针变量的地址进来。

常量与普通变量的区别--存储位置不同,生命周期不同

默认情况下,一个线程必须被等待,若不等待,则会造成资源泄露。

线程分离:将线程joinable属性修改为detach属性

一个线程属性若是joinable那么就必须被等待

一个线程属性若是detach那么这个线程退出后则自动释放资源,不需要被等待(因为资源已经自动被释放了)

为什么要分离线程?

分离一个线程,一定是对线程的返回值不感兴趣,根本不想获取,又不想一直等待线程退出,这种情况才会分离线程。

int pthread_detach(pthread_t thread);--将指定的线程分离出去--属性修改为detach

pthread_detach(pthread_self());--返回调用线程的tid,自己分离自己,实际就是设置一个属性。

分离线程,只是说线程退出后自动释放资源。

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/1/94850.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息