Linux解锁线程基本概念和线程控制,步入多线程学习的大门(2)
2.4.线程等待:
为什么需要线程等待?
已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。不然也会造成内存泄露问题!
创建新的线程不会复用刚才退出线程的地址空间。
主线程退出 == 进程退出 == 所有线程都要退出
1、往往我们需要main thread最后退出
2、线程也要被”等待“,要不然会产生类似进程那里的内存泄漏问题
pthread_join函数
功能:等待线程结束
原型
int pthread_join(pthread_t thread, void **value_ptr);
参数
thread:线程ID
value_ptr:它指向一个指针,后者指向线程的返回值
返回值:成功返回0;失败返回错误码
2.5. 分离线程
分离线程的概念:
默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。这就是分离线程
int pthread_detach(pthread_t thread);
可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离:
pthread_detach(pthread_self());
被分离的线程不需要join,虽然线程已经被分离了,但是资源还是要共享,所以当主线程退出时,被分离的线程也是要退出的,底层也是同一个进程!只是不需要退出了。我们希望主线程是最后一个退出的。所以一般程序的主线程永远不会退出,是永驻进程!
3.关于进程和线程比较的深层次问题
3.1已经有多进程了,为什么还要有多线程??
创建一个进程需要创建PCB,地址空间,页表,加载代码与数据,创建文件缓冲区等很多操作,但创建一个线程,只需要创建一个PCB,复用原本的地址空间。创建进程的成本比创建线程高很多!切换进程时不仅仅要更换上下文数据,更换地址空间等很多操作,切换线程只需要切换PCB!!!线程删除成本也很低。
但是线程也有缺陷:一个线程出错(野指针)就是这个进程出错了,因为他们使用同一个地址空间,所以其他的线程也会报错退出!!! 线程的健壮性很差!而进程是独立的互不影响!进程和线程各有特长!
3.2线程的切换vs进程的切换
CPU里面的cache会将后续代码提前缓存到cache(默认会从后续代码编译,如果调用函数跳转代码就会读取失败,也就是cache命中失败,但命中失败的概率较低),这样就不用一直向内存中提取内容,大大提高了CPU寻址的效率,所以以后CPU读取数据的时候,就不需要从内存中读取数据了,直接从CPU里面的cache里面读取
所以进程切换不仅仅要考虑到寄存器之间的变化,但也要考虑cache的切换,cache可能只对上一个进程有效,切换进程之后,就会丢弃,重新寻址,这部分要消耗的成本很大
但是如果是线程切换,寄存器、上下文代码同样也要变化,但是因为是线程,共享同一份代码所以cache不需要更换,因此就减少了很多的成本。
因此线程切换要比进程切换更简单,消耗的成本更低!
4.总结:
4.1.线程的优点:
创建一个新线程的代价要比创建一个新进程小得多
与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
线程占用的资源要比进程少很多
能充分利用多处理器的可并行数量
在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现I
/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作
4.2.线程的缺点:
健壮性降低
编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。
缺乏访问控制
进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。
编程难度提高
编写与调试一个多线程程序比单线程程序困难得多
4.3.线程异常:
单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃
线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也就随即退出
4.4.当主线程结束之后,新创建的被分离的子进程也会被终止,为什么都分离了还要终止呢?
因为线程结束,就代表整个进程结束,线程之间是共享资源的哦!
虽然线程的大部分资源是共享的,但是有一部分资源是独立的,比如线程的栈空间,线程的局部存储
4.5.多线程的全局存储和局部存储
如果不同的线程对一个全局变量进行修改,那么这个全局变量会随着每个不同线程的操作,而作改变。因为全局变量对于不同的线程在已初始化数据当中存储,属于共享资源,也不像不同的进程之间会有写时拷贝,所以不同的线程对一个相同的全局变量做修改时,会随时改变
线程的局部存储就是在全局变量前面设置一个前缀 __thread 标识该全局变量是线程的局部存储,因此局部存储在不同的线程当中值是不一样的,属于每个线程的独立资源,也就是虚拟地址并不相同。