linux

您所在的位置:网站首页 linux线程进程是做什么 linux

linux

2024-07-12 05:02| 来源: 网络整理| 查看: 265

文章目录 线程引言线程特征线程具体概念线程基本概念线程是什么?从内核角度去分析 进程与线程的比较总结进程ID和线程ID线程管理线程创建线程ID及进程地址空间布局线程终止线程等待线程分离线程绑定

线程引言

问题:为什么有了进程的概念后,还要再引入线程呢?什么的系统应该选用多线程?

从多任务操作来看,相比于多进程,多线程是一种非常"节俭"的多任务操作方式。因为在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据(独有和共享机制),并且启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。线程间方便的通信机制。对不同的多个进程来说,它们具有独立的数据空间,要进行数据的传递只能通过进程间通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。但是,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改(涉及可重入、竞态条件等概念),有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。除了以上所说的优点外,不和进程比较,多线程程序作为一种多任务、并发的工作方式,当然有以下的优点:   1. 提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(time consuming)置于一个新的线程,可以避免这种尴尬的情况。   2.使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。   3. 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。 线程特征

线程的优点

创建一个新线程的代价要比创建一个新进程小得多与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多线程占用的资源要比进程少很多能充分利用多处理器的可并行数量在等待慢速I/O操作结束的同时,程序可执行其他的计算任务计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。

线程的缺点

性能损失:一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。换句话说就是线程在调度的时候是有开销的,如果大量的进程,会导致程序在频繁的切换,占用CPU去执行上下文信息,程序计数器等同步和调度开销。

健壮性降低:编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。

缺乏访问控制 进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。

编程难度提高编写与调试一个多线程程序比单线程程序困难得多

线程异常

单个线程如果出现除零,内存越界,野指针问题导致线程崩溃,进程也会随着崩溃线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也就随即退出

线程用途

合理的使用多线程,能提高CPU密集型程序的执行效率合理的使用多线程,能提高IO密集型程序的用户体验(如生活中我们一边写代码一边下载开发工具,就是多线程运行的一种表现) 线程具体概念

内核线程

操作系统内核支持多线程调度与执行内核线程使用资源较少,仅包括内核栈和上下文切换时需要的保存寄存器内容的空间

轻量级进程( lightweight process,LWP )

由内核支持的独立调度单元,调度开销小于普通的进程系统支持多个轻量级进程同时运行,每个都与特定的内核线程相关联

用户线程(我们编写多线程程序时实际写的都是用户线程)

建立在用户空间的多个用户级线程,映射到轻量级进程后调度执行用户线程在用户空间创建、同步和销毁,开销较低每个线程具有独特的ID 线程基本概念 在一个程序里的一个执行流就叫做线程(thread)。更准确的定义是:线程是“一个进程内部的控制序列”一切进程至少都有一个执行线程线程在进程内部运行,本质是在进程地址空间内运行在Linux系统中,在CPU眼中,看到的PCB都要比传统的进程更加轻量化透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流

在这里插入图片描述

线程是什么?从内核角度去分析

线程就是创建出来的执行流,在内核当中创建了一个PCB, 在内核当中创建了-个task struct这样的结构体; 1.回顾创建子进程

fork:在内核当中以父进程的PCB为模板拷贝父进程PCB当中的内容创建一个PCB , PCB被内核使用双向链表管理起来vfork:在内核当中以父进程的PCB为模板拷贝父进程PCB当中部分内容创建一个PCB ,子进程的PCB是指向父进程的虚拟地址空间的, PCB被内核使用双向链表管理起来;但是存在调用栈混乱的问题(由于使用同一虚拟内存空间,所有的调用函数都压栈在同一个栈中,势必在调用函数时造成混乱),故规定先调用子进程的调用函数,即让子进程先运行,进而在让父进程在运行

2.再看创建线程

pthread_ create函数:在内核当中创建一个PCB , PCB是指向父进程的虚拟地址空间的,内核会给创建出来的线程在虚拟地址空间当中的共享区开辟一段空间,保存线程独有的东西,所以由于线程的独有和共享属性,线程可以实现多任务调度与执行,且不存在调用栈混乱的问题。注意:fork,vfork都是系统调用,而pthread是c库函数,但是三者底层也都是调用的clone函数注意:Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a。

在这里插入图片描述

进程与线程的比较总结 线程空间不独立,有问题的线程会影响其他线程;进程空间独立,有问题的进程一 般不会影响其他进程创建进程需要额外的性能开销线程用于开发细颗粒度并行性,进程用于开发粗颗粒度并行性线程容易共享数据(全局变量和堆内存地址空间相互独立全局数据也会拷贝出单独一份),进程共享数据必须使用进程间通讯机制(共享内存,管道等)线程是操作系统调度的最小单位,进程是操作系统分配资源的最小单位注意:线程和进程不一样,进程有父进程的概念,但在线程组里面,所有的线程都是对等关系 进程ID和线程ID

在Linux中,目前的线程实现是Native POSIX Thread Libaray,简称NPTL。在这种实现下,线程又被称为轻量级进程(Light Weighted Process),每一个用户态的线程,在内核中都对应一个调度实体,也拥有自己的进程描述符(task_struct结构体)。 没有线程之前,一个进程对应内核里的一个进程描述符,对应一个进程ID。但是引入线程概念之后,情况发生了变化,一个用户进程下管辖N个用户态线程,每个线程作为一个独立的调度实体在内核态都有自己的进程描述符,进程和内核的描述符一下子就变成了1:N关系,POSIX标准又要求进程内的所有线程调用getpid函数时返回相同的进程ID,如何解决上述问题呢?所以Linux内核引入了线程组的概念。

struct task_struct { ... pid_t pid; //线程id pid_t tgid; //线程组id ... struct task_struct *group_leader;//指向主线程的指针 ... struct list_head thread_group; ... };

多线程的进程,又被称为线程组,线程组内的每一个线程在内核之中都存在一个进程描述符(task_struct)与之对应。 1.进程描述符结构体中的pid,表面上看对应的是进程ID,其实不然,它对应的是线程ID; 2.进程描述符中的tgid(线程组ID),含义是Thread Group ID,该值对应的是用户层面的进程ID。 在这里插入图片描述

线程组内的第一个线程,在用户态被称为主线程(main thread),在内核中被称为group leader,内核在创建第一个线程时,会将线程组的ID的值设置成第一个线程的线程ID,group_leader指针则指向自身,既主线程的进程描述符。所以线程组内存在一个线程ID等于进程ID,而该线程即为线程组的主线程。

至于线程组其他线程的ID则有内核负责分配,其线程组ID总是和主线程的线程组ID一致,无论是主线程直接创建线程,还是创建出来的线程再次创建线程,都是这样。

总结

使用ps aux看出来的进程id就是tgid1.当当前程序只有一个执行流的时候,意味着当前程序只有一个 线程,我们将执行main函数的线程,称之为主线程 tgid(线程组id(进程id))等于主线程的id ( pid )2.当程序有多个执行流的时候.意味着当前程序有多个线程 对于 主线程:执行main pid == tgid 对于工作线程:新创建出来的执行流 tgid:相当于进程号(线程组id ) ,是一定不会变的 ,标识当前的线程属于哪一个进程 pid:相当于线程id,每一个线程id都是不同的 线程管理

线程创建,线程终止,线程等待 Linux系统下的多线程遵循POSIX线程库接口,称为pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用库libpthread.a。所以要使用这些函数库,要通过引入头文,而且链接这些线程函数库时要使用编译器命令的“-lpthread”选项。

线程创建 int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*thread_ start)(void*), void *arg); 参数说明 : thread:指向线程线程标识符的指针,和线程id并不是一回事,pthread_t ,是线程独有空间的首地址,通过这个标识符可以对当前的线程进行操作, 调用pthread_create作为出参返回给调用者 attr:线程属性,pthread_ attr_t 是一个结构体,这个结构体完成对新创建线程属性的设置;如果说,创建线程的时候,该参数被设置为NULL ,则认为采用默认的属性 属性值不能直接设置,须使用相关函数进行操作,初始化的函数为pthread_attr_init,这个 函数必须在pthread_create函数之前调用。属性对象主要包括是否绑定、是否分离、堆栈地址、堆 栈大小、优先级。默认的属性为非绑定、非分离、缺省1M的堆栈、与父进程同样级别的优先级。 线程栈的大小 线程栈的起始位置 线程的分离属性 线程的优先级调度属性 thread_ start:回调函数:线程入口函数地址,这个函数是void*返回值, void*参数 arg:给线程入口函数传递的参数的值 不能传递临时变量 可以传递一个堆上开辟的内存到线程入口函数当中去使用,但需要在线程入口函数结束的时候,将对上开辟的内存释放掉(防止内存泄露) void*传递任意的类型,包含自定的数据结构,类实例化指针对象 返回值:成功返回0;失败返回错误码常见的错误返回代码为EAGAIN和EINVAL。前者表示系统限制创建新的线程,例如线程数目过多了;后者表示第二个参数代表的线程属性值非法。 #include #include #include typedef struct thread_info //构造thread_info动态对象,作为线程函数参数传递给线程 { int thread_num_; thread_info() { thread_num_ = -1;//初始化 } }THREADINFO; void* thread_start(void* arg)//回调函数,即线程入口函数 { THREADINFO* ti = (THREADINFO*)arg; while(1) { printf("i am new thread~~~~: %d\n", ti->thread_num_); sleep(1); } //对于传递进来的对上开辟的内存,可以在线程入口函数结束的时候,释放掉,不会导致程序有内存泄漏的风险 delete ti; return NULL; } int main() { pthread_t tid; int ret; int i = 0; for(; i thread_num_ = i; ret = pthread_create(&tid, NULL, thread_start, (void*)threadinfo);//循环创建线程 if(ret

进程间通信进程信号进程控制进程理解


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3