服装网站推广策划书,网络运营推广,沙漠风网站建设,国外对旅游网站的建设线程已经成为调度的基本单位了#xff0c;每一个线程都属于同一个地址空间中#xff0c;所有的线程都属于同一个进程 换句话任何一个线程尝试调用geipid它应该是同一个pid 可是OS选择线程时#xff0c;他怎么知道哪个线程是主线程#xff1f;哪个是新线程#xff1f;线程也…线程已经成为调度的基本单位了每一个线程都属于同一个地址空间中所有的线程都属于同一个进程 换句话任何一个线程尝试调用geipid它应该是同一个pid 可是OS选择线程时他怎么知道哪个线程是主线程哪个是新线程线程也有主次之分 每个线程都是一个调度的基本单元所以每个线程都要有自己调度的id值啊反正记着每一个线程也要有一个id
正是因为OS用进程内核数据结构模拟的线程所以内核中有没有很明确的线程的概念呢 没有的。它只有一个轻量级进程的概念 既然他只有轻量级进程的概念的话注定了linux os不会给我们直接提供线程的系统调用只会给我们提供轻量级进程的系统调用 可是我们用户需要线程的接口 linux程序员提供了pthread线程库 – 应用层 – 轻量级进程接口进行封装。 为用户提供直接线程的接口 这是一个第三方库几乎所有的Linux平台都是默认自带这个库的! Linux中编写多线程代码需要使用第三方pthread库!!
那到底怎么使用这个库呢? 快速使用一下线程接口
创建 pthread_t 就是一个无符号长整型
返回值成功返回0 如果失败的话用返回值的形式来告诉错误码是几它没有使用errno 一旦调用pthread_create创建线程成功时新线程转而执行threadRoutine该执行流从上往下执行如果是死循环那线程永远执行 如果结束了那新线程也就结束了 而主线程继续向后执行主线程一开始就有了main函数是他的入口函数 所以一个进程内会有两个执行流分别执行main函数后续代码和新线程指向的新代码threadRoutine 因为main函数和指定函数threadRoutine 它在同一块代码被编译时一定使用代码部分不同的地址空间范围代码区虽然是一整块但不同函数用的不同的地址空间注定了这两个线程执行时在代码资源上是分离的。就是说的代码资源分配 这是第三方库不属于c/c语言如果不指明用哪一个库会出现链接错误 那么-I -L怎么不带上呢 因为pthread库在系统里已经默认安装了编译器能找到库的路径的只是不知道要链接哪一个库而已 用ps axj查进程 会发现只有一个进程这个进程里有两个执行流
我们可以用新命令 ps -aL 查看当前用户启动所有轻量级进程 LWP是什么呢 Linux中CPU调度基本单位是线程线程在Linux当中叫做轻量级进程cpu调度时不仅仅只看pid 更要看到每一个轻量级进程也要有自己对应的标识符所以轻量级进程就有了LWP叫做light weight process id 一个是6631 一个是6632 所以cpu调度执行流是按照LWP来进行调度的 所以CPU根本就不看PID他看的是LWP所以你以前是不是讲错了以前不是按照进程为单位来调度的每个进程都要有标识符吗 仔细看上面的执行流它的PID 和LWP 是相同的证明了 1 . 上面线程叫做主线程因为PID LWP 6631 最先有的线程是它 2.下面那个线程 PID LWP 说明他是被创建出来的 OS就能根据LWP和PID是否相等来决定你是否是主线程。
所以以前调度时我们讲错了吗并没有 以前启动的每一个进程它调度时拿PID和拿LWP是一样的效果 单进程不就是一个进程内只有一个线程
LWP是调度的基本单位OS调度时看LWP但是他也能够确定哪一个是主线程哪一个是新线程
发信号杀掉任意一个线程默认都会导致整个进程被杀掉 那这个信号算是发给线程的还是发给进程的 我们认为他是发给进程的因为每一个线程都是进程的执行分支你发给线程就是发给进程的。
今天我们再写一个show函数这个函数可以被多个执行流同时执行那么show函数被重入了哦 今天再定义一个全局变量所有线程都是共享的。
为什么都能看到因为它们共享地址空间啊。 所以线程之间要通信很容易如果定义一个大缓冲区一个线程写一个线程读那不就俩通信了吗 所以线程中天然两者之间看到的资源共享为通信方便提供了很好的技术准备 线程的共享性容易实现他是先进性的表现
如果线程异常了导致进程收到浮点数错误所以整个进程被干掉了 我现在挺好奇pthread_create形参的那个控制线程的tid就想打印出来看看 你这个tid 也不是 LWP啊那你这个tid是啥 我们把tid的打法改为%p真像个地址。 所以tid并不是直接是线程的LWP因为LWP是OS层的概念只要OS自己知道就行了 作为用户不关心我只关心tid是给用户去使用的
等待 一个新线程一旦被创建出来了是主线程先跑还是新线程先跑呢 不确定调度器说了算 谁应该最后退出呢 肯定是主线程 为什么老是让他最后退出呢? 谁让你主线程创建了新线程创建新线程本质就是在对线程做管理 所以你既然要管不把人家新线程管完算什么管理呢 你不能自己先退了让人家新线程怎么办。 目前所知其实主线程要是退了一般代表进程退出了所以新线程就跟着退 谁先运行不清楚一定要保证主线程最后退
那我主线程就当甩手掌柜死循环干我自己的事情为啥不让主线程退因为你得管我新线程 我新线程要是退了你不管吗 所以新线程退出时也要被等待如果你主线程不等我也会造成类似子进程退出父进程不等待 的情况造成类似于僵尸进程的问题 一句话 新线程被创建一般也要被等待如果你不等默认会导致类似僵尸进程的问题 这个代码我们没办法验证因为新线程一退查也查不到了但确实存在这个问题
更重要的是为什么要创建一个线程啊我把新线程创建出来就是为了办事情的。 你把事情办的如何我怎么知道还是我不关心了 所以新线程运行结果数据你是不是也得给我主线程
所以线程等待 1.防止新线程造成内存泄漏 2.如果需要你也可以获取新线程的退出结果
怎么等待呢
pthread_join 等待一个终止的线程 pthread_t tid 传入你要等待哪个线程
返回值成功返回0失败返回错误码 线程类函数所有出错码不用errno统一用返回值返回他不用全局的
新线程一旦把自己入口函数执行完了默认线程就退出了 新线程退出不会影响主线程
主线程等待的时候默认会阻塞式等待新线程 现在已经可以用pthread_join保证新线程不退我主线程也不退阻塞等待
现在的问题是主线程怎么知道新线程执行完毕的执行结果如何 一个线程执行完毕的执行结果最终通过返回值让我们知道的。 现在的问题是你的主线程怎么知道新线程的返回值呢他们可是两个执行流
你是两个线程一个主一个新 两个task_struct在内核中
新线程的返回值返回给用户层主线程在用户层可以通过pthread_join来吧新线程的返回值拿到 不管是pthread_join库函数接口 还是新线程的返回值都属于pthread库的内部 新线程执行完返回值直接写到pthread库里 你要获取库里面的void* 用户自己需要定义一个void* 变量取地址传给形参 join里面第二个形参二级指针void** 在join里面这个形参解引用再把库里面新线程返回值赋值给这个形参解引用上层用户定义的void* 变量就拿到了新线程的返回结果。 有点问题一个执行流退出结果有三种呢为什么这里怎么不管线程出异常了 线程也有可能出异常啊为什么你join只有退出码不考虑异常呢
答它做不到一旦线程出异常主线程也就跟这遭殃了join还返回啥呢。 异常问题是进程考虑的。 你线程hold不住你只考虑退出码这种情况就行
终止
如果在任何一个线程中直接exit( ) 会直接终止整个进程 exit是用来终止进程的不能用来终止线程。
主线程要是退了整个进程也跟着退你别说线程还没执行完线程也要退
1、线程函数中直接 进行return 就代表线程退出 2、pthread_exit( ) 谁调用就终止谁参数就是线程函数返回值return退出码
3、线程取消pthread_cancel 不常见
前提是线程真的已经创建出来了
形参是要取消哪一个进程 tid
如果一个线程本身是被取消的该线程退出结果是PTHREAD_CANCEL是宏-1 根据目前知识线程的大部分资源都是共享的 正文代码其实也是共享的只不过每个线程一人一块代码单独写个公共方法函数被两个线程都可以读到函数是随时可以被重入
全局变量也是可以多线程访问的
共享区两个线程都在用cout printf,说明共享区也是被所有线程共享的
栈一定是被所有线程私有的
命令行环境变量今天不考虑
关键在于堆区重谈线程入口函数参数和返回值
线程的参数和返回值不仅仅可以用来进行传递一般参数也可以传递对象! 不要狭隘的认为参数传个字符串返回值返回个整数 我们现在整个代码都是在堆上new的发现代码互相交叉式的 在主线程New了一个对象传递给了新线程在新线程new了一个对象传递给了主线程 说明什么 说明堆空间也是被线程共享的 这不会出问题吗一般不会 因为堆空间指针谁拿着指针谁就访问堆空间 目前我们的原生线程pthread库属于原生线程库
c11语言本身也已经支持多线程了 VS 原生线程库 他们两个什么关系
别和我说什么C11支持什么多线程它的底层实现就是封装原生线程库 编译时如果你不加 -lpthread 就找不到这个库 重新理解一下什么叫做原生线程你刚刚看了pthread_create会产生一个线程ID 这个ID可不是内核级别的LWP那个数字他们俩不一样 现在我想知道这个线程ID是什么
另外你还说线程都有自己独立栈结果你怎么证明 clone和fork底层原理类似创建子进程 clone专门用来创建轻量级进程的我们不用这个接口看一下复杂的接口参数就知道了 现在的问题是clone我们用不了有些接口系统不让用这个接口就被pthread线程库封装了 给我们提供了create , join…这样的接口 上层用户要有线程的概念也就是线程库 clone允许用户在应用层传入1个回调函数和1个用户空间来代表轻量级进程在运行过程中 1.它要执行的代码 2.它在运行过程中形成的临时变量
线程库要封装clone每一个线程都要给它提供执行方法线程库内部还要开辟空间 把方法交给clone里面的回调函数栈也交给它 方法最终暴露出去就是你自己创建线程线程时pthread create 传的回调函数栈结构你是没管的
也就是说线程的概念是库给我们维护的。 问题 当你自己执行多线程代码时用原生线程库时你这个库要不要加载到内存中加载到哪里 原生线程库可是一个动态库啊 答
要加载到内存里别跟我扯什么线程我就是一个进程 执行时这个库一定被加载到内存中经过页表把pthread库映射到了共享区里面 不要觉得共享区只有c/c库 对应pthread库里面每一个创建好的线程在库里面就要给我们开辟出一段空间用来充当新线程的栈。 所以线程库要加载到内存未来所有讨论都是基于内存的
所以我的线程的ID是多少我的栈是多大我线程回调方法地址是什么我线程时间片是多少 所有这些字段请问OS关不关心知不知道 不知道因为它没有线程概念 但是这些所有属性叫做线程的属性 线程的概念是库给我们维护的这句话该怎么理解呢 线程概念既然是库给我们维护意味着线程库中要维护线程主要维护线程的概念 不用维护线程的执行流 意思就是说线程底层就是轻量级进程执行流tast_struct但是线程相关很多用户关心 的属性 必须由库来维护
线程库里面同时会存在多个被创建的线程很正常 线程库注定要维护多个线程属性集合。线程库要不要管理这些线程呢 要先描述在组织 所以每创建一个线程在线程库中就要创建线程库级别的线程控制块 它包含了线程很多属性对上为用户提供诸如这个线程回调函数在哪里独立栈在哪里 线程的id是多少 更重要的是线程的lwp指向底层哪个执行流task_struct用户未来访问线程时它只要找到线程控制块执行流task_struct自然而然就会由OS底层自动调度它就执行你上层的代码了 你想获取线程的属性你就通过线程控制块获取吧
所以线程是由用户层维护的他是在OS之上的称为用户级线程 用户级线程什么鬼呢说白了就是由用户把线程的结构体维护起来。
所以再来看这张图
这是某一个PCB的进程地址空间这是你加载到内存的pthread.so库 库已经加载到内存了然后被映射到进程地址空间共享区的 你这个进程一把线程另一个进程也可以一把线程不要紧你们都映射同一个库 随着线程创建越来越多每一个线程在创建时都在库里创建一个线程tcb tcb包含了线程很多属性每个线程都有这个结构所以在这个库里面把所有tcb按照数组方式给我们维护好先描述在组织就有了 未来访问线程。比如等待线程获取线程退出结果退出结果就是写在它的局部存储里 你要形成变量时要对数据做压栈你就用线程栈。 未来你有十个线程就有10个tcb 为了快速找到每一个tcb在共享库里所以把一个tcb在内存中起始地址叫做线程的tid 为什么要用这个起始地址作为tid呢 1.它存的是地址 2.它在用户空间 3.是虚拟地址可以直接访问 所以线程你想获取他的属性直接拿着tid库函数他就直接找到属性了 下面是时候谈 线程栈了
每一个线程运行时一定要有自己独立的栈结构 因为每一个线程都会有自己的调用链 注定了每个线程必须有自己独立调用链所对应的栈帧结构
其中 主线程直接使用地址空间中的栈即可
我们剩下的新线程首先在共享区线程库里面为新线程创建tcb 起始地址作为线程tid, 线程控制块包含默认大小一段空间叫做线程栈 然后要在内核中创建执行流pcb它就在库中调clone, 把对应线程执行方法和线程栈传递给clone 所以clone执行流调用时中间形成的临时数据都会压入到这个tcb里面的线程栈
换句话说所有对应非主线程它的栈都在库中维护即共享区维护 除了主线程所有其他线程的独立栈都在共享区具体来讲是在pthread库中tid指向的用户tcb中!