万网做网站花多少钱,建设行业个人信息网站,安卓wordpress,国家企业信息公示网查询官网网址【Linux系统编程二十六】#xff1a;线程控制与线程特性 一.Linux线程库pthread1.线程控制块2.线程tid3.线程栈 二.线程控制1.线程创建2.线程退出3.线程等待 三.线程的特性1.独立栈2.局部存储3.线程可分离 一.Linux线程库pthread
在Linux中#xff0c;是没有明确的线程概念的… 【Linux系统编程二十六】线程控制与线程特性 一.Linux线程库pthread1.线程控制块2.线程tid3.线程栈 二.线程控制1.线程创建2.线程退出3.线程等待 三.线程的特性1.独立栈2.局部存储3.线程可分离 一.Linux线程库pthread
在Linux中是没有明确的线程概念的因为在内核里线程是用进程的内核数据结构模拟实现的只有轻量级进程的概念。所以在内核里它没有直接控制线程的接口只有通过轻量级进程的系统调用 简单介绍一下该系统调用第一个参数就是一个函数指针是该线程要执行的方法函数。第二个参数是一个栈指针指向的是一块栈空间。至于为什么要栈后面会讲到。 而我们用户只想要线程不想要所谓的轻量级进程所以就有人在应用层给我封装了一个线程库pthread。 它内部就是封装了调用轻量级进程系统调用接口然后给我封装出一批创建线程退出线程等待线程的接口。 所以我们想使用Linux中的线程需要使用第三方pthread库。
1.线程控制块
我们要理解的是操作系统内部是没有线程的只有轻量级进程线程是在用户层创建出来的。是用户层维护线程的所以叫做用户级线程。
因为库是共享的你在库中可以创建线程我也可以创建线程所以线程库中会存在很多的线程这些线程需不需要管理呢当然需要 所以线程库需要维护线程要管理线程—先描述再组织。 在线程库中其实是存在一个叫做线程控制块的结构体它就是用来描述线程的各种属性比如线程的字段线程的独立栈线程的回调函数等。
创建一个线程线程库就需要开辟一块空间用来创建线程的TCB。 这是在用户层面而线程库使用时是需要加载到内存面的在内核层面没有所谓的TCB(线程控制块)它只有一个执行流也就是轻量级进程对于操作系统来说它管理的是执行流。每一个线程在内核里都有唯一的LWP。
2.线程tid
根据上面的理解我们知道在用户层面线程库会为线程创建线程控制块TCB。那么这么多线程用户是如何区别这些线程的呢每一个线程在用户层都会存在一个tid用来区别不同线程的。根据动态库的学习我们知道在链接时需要将动态库加载到内存的共享区里。 而线程库链接时也需要加载到内存的共享区里。 线程库里存在很多描述线程的线程控制块TCB。线程库按照数组的方式将各个线程控制块管理起来。所有的线程都在动态库里被管理着。 而这些线程控制块在共享区里的起始地址就就是该线程的tid。 因为为了更好的找到每个TCB在共享库里的位置每一个TCB在内存的起始地址就称为线程的tid
所以线程的tid本质就是共享区内存的一个虚拟地址。
线程获取自己的tid系统调用接口
3.线程栈
线程控制块里还存在一个线程栈这是什么又为什么呢 因为每一个线程都是一个独立的执行流各自在不同的地址空间执行代码也就是执行不同的函数。而函数里难免会需要各种临时变量或者调用其他函数传递形参等这些都需要栈空间存储。所以每一个线程创建时都需要一块栈空间用来维护各自函数里临时变量。 而主线程用的是地址空间里的栈空间。其他线程用的都是共享区里开辟的空间。在线程创建时首先在上层线程库会为新线程创建一堆数据结构叫做线程控制块里面维护着该线程的tid该线程的栈空间等。上层将tcb创建好后在内核层面就会形成对应的执行流然后调用系统调用clone将线程的栈空间传递给child_stack。
二.线程控制
1.线程创建
main函数执行流我们称为是主线程主线程创建的线程我们称为新线程。 1.pthread_create()是用来创建线程的。它会产生一个线程id,用来标识该线程的唯一性。线程的后续操作都是根据该线程的id来操作线程。该id的本质就是共享区内存的虚拟地址。(就是线程控制块TCB在共享区内存的起始地址) 2.pthread_t是操作系统提供的数据类型类似于int 3.第一个参数是用作输出型参数的它是将创建的线程的tid给带出来。 4.第二个参数是用来控制线程的属性不用的情况下置nullptr就可以。 5.第三个参数是一个函数指针它是该线程要执行的方法函数。该函数的返回值和参数都是void类型。 6.第四个参数是新线程执行的方法函数的参数。执行线程函数也可能需要参数所以这个参数就是主线程传给新线程的参数。主线程想让子线程知道什么就可以通过该参数传递给子线程。该参数是一个void类型不过注意不仅可以传普通类型过去还可以传对象过去。 7. 注意在编译的时候需要链接线程库。 创建的线程和主线程是如何区分的呢 根据LWP来区别或者根据它们的tid来区别。 任何一个线程被干掉进程就会被干掉因为线程就是进程内部的一个执行分支。
2.线程退出
线程执行的函数返回了就代表线程退出。 线程退出有两种方式一种是函数之间return返回退出。 一种是使用系统调用pthread_exit()来退出。 注意不能使用exit()接口它是用来进程退出的
注意线程退出返回的是void*类型如果没有什么可以退出的就直接返回nullptr; 形参可以接收一个对象指针那么最后函数返回时也可以返回一个对象。
3.线程等待
主线程创建新线程也需要观看这个新线程需要等待新线程。不然会出现类似于僵尸进程。导致资源泄露。并且有时主线程也需要获取创建的线程的一些处理结果。所以需要主线程最后退出。 1.pthread_join()是用来进程等待的。 2.第一个参数就是线程的tid创建线程时会将线程的tid给带出来。 3.第二个参数retval是一个输出型参数是用来将线程执行函数结果给带出来因为线程执行的函数的返回值是void类型要想将该类型带出来必须使用指针类型也就是void *类型。 4.主线程在等待时默认是阻塞等待如果线程不退出那么主线程就会一直等待。 5.线程退出时主线程join等待不需要考虑异常情况因为做不到只有进程才可以做到。 6.一般来说主线程退出其他线程也会退出但是如果使用pthread_exit(主线程tid),其他线程可能不会退出。 主线程还可以取消新线程(该线程已经被创建完毕) 线程如果被取消那么它执行的函数返回值就会为-1。
三.线程的特性
因为在内核里没有线程的具体实现线程是在用户层利用第三方库实现的所以在内核中是如何区分线程和进程的呢在内核中是没有线程和进程概念它们都是轻量级进程而每一个轻量级进程都有唯一标识它的LWP(Light Weight Process)。 而与之对应的用户层是真正实现了线程所以叫做用户级线程每一个线程都具有自己的tid。 所以这样对应起来真正的线程是具有两部分构成的一个是上层用户级线程一个是下层内核级线程。用户级执行流内核LWP11
1.独立栈
每一个线程都是具有独立的栈结构的当不同的线程调用同一个函数时虽然是同一个函数但是在不同的函数栈帧中。所以同一个变量是在不同的栈帧中开辟的地址也就不会相同。
但这个独立是相对独立不是真的独立私有其他线程想要访问你的栈也是可以访问到的因为这些线程都是在同一个地址空间上运行所以在线程中是没有秘密可言的只不过线程之间最好不要越界访问。
2.局部存储
全局变量是每一个线程都可以访问的就相当于一个共享资源。访问到的都是同一个。 那么有没有这样的变量它是线程独有的但又是全局的呢也就是线程想要一个私有的全局变量呢
线程的局部存储可以满足这样的条件。什么叫局部存储呢? 在线程控制块里存储着一个叫线程局部存储的对象。它也是一块空间是在共享区内存提前申请好的当线程创建时就已经存在。也就是每一个线程都会有一个局部存储它可以用来定义全局变量但这个全局变量又只有该线程能访问。 可以将线程自己不变的属性且频繁调用的放进去。然后再交给线程。 可能有人会疑惑为什么要有局部存储为什么不在线程函数内部直接设置一个临时变量不就好啦。 这样做的原因很简单效率高不需要频繁的创建变量不需要传递参数也可以使用。 3.线程可分离
主线程创建新线程后有两种情况一种是等待新线程一种是不等待线程。 等待线程可能需要获取新线程的返回结果。 不等待线程说明不关心新线程的返回结果。
但是你又说了不等待线程会造成资源泄露。 有一种方法可以让主线程不等待新线程也不会造成资源泄露线程分离
也就是主线程与新线程分离后主线程不关心新线程的返回结果新线程退出后会自动释放资源。
int pthread_detach(pthread_t thread);
//thread就是线程的tid线程分离有两种方法 ①主线程强制分离 在主线程里利用创建线程获取得到的tid来分离新线程。 ②新线程主动分离 在新线程执行的函数里利用(pthread_self()获取自己的tid来分离。 【注意】 ①一个线程被分离后主线程就不能再等待它了。如果该线程分离后主线程又等待它这样会报错。 ②分离线程说明主线程是不关心该线程的返回结果也就是不想等待该线程但还是要保证主线程最后退出。