目录
一、线程的退出与等待
1.1pthread_join线程等待
1.2线程异常
1.3线程如何退出和结束
编辑 二、线程切换
三、线程的优缺点
3.1优点
3.2缺点
3.3线程vs进程
四、多线程的使用及实操
4.1堆空间共享
一、线程的退出与等待
在Linux中线程具有如下的特点:
1、主线程退出=进程退出=所有线程都要退出。
2、线程也要被"wait",不然会产生和进程类似的问题,造成内存泄露。
3、新线程和主线程的运行先后顺序是不确定的,由调度器来决定。
1.1pthread_join线程等待
int pthread_join(pthread_t thread,void** retval)
等待一个指定的线程,如果该线程正常退出则返回0,否则返回错误码。如果目标线程没有退出,调用join的线程则会阻塞等待。
而retval实际上是一个输出型参数,用来让调用join的线程拿到目标线程的具体属性和数据。 因为c语言的缘故void类型可以用来接收任意类型,所以我们要想让调用join的线程拿到目标线程的具体信息,目标函数的返回值类型就应当是一个void*类型而retval之所以是void**则是因为我们要手动的创建一个void*的类型来接收和保存目标线程所返回的void*,而要更改void*类型,就需要用到void**。
1.2线程异常
多线程中任何一个线程出现异常都会导致退出则会导致整个进程退出,所以多线程的代码往往容易出现健壮性不好的问题。
所以:join不考虑线程异常的情况!因为线程一旦出现异常,整个进程就会跟着退出,join就等待不到目标线程了。
1.3线程如何退出和结束
1、线程函数正常结束,即代码跑完了。
2、不能用exit直接退出,因为exit是用来终止进程的。它会让整个进程直接挂掉。
3、pthread_exit()终止一个线程。
pthread_exit((void*)123);
退出当前线程,退出码123
4、pthread_ cancel()取消一个线程
给指定的线程发送退出信号。
pthread_cancel(tid);//tid为pthread_t类型
而接收到退出信号的线程所返回的退出码就为-1,而-1实际上就是PTHREAD_CANCELED。
二、线程切换
ls cpu查看CPU中的详细信息。
说到线程切换,其令人首先想要对比的数据就是进程切换,在CPU中存在着大量的寄存器,在进程切换时寄存器中所保存的大量的进程信息以及上下文数据会被加载到正在执行的进程PCB中,然后再将新的进程的上下文数据以及代码等信息加载到CPU中,然后再去执行新的进程,而在Linux操作系统中,线程是由进程进行模拟的在CPU看来线程也是和进程一样的PCB,不过是更加轻量级的进程,而多数人往往存在一个误区,那就是进程间切换和线程间切换所消耗的时间和成本没有区别,因为其二者都是PCB结构,切换时都是进行上下文数据的保存。但着其中存在着一个很大的误区,CPU中除了存在大量的寄存器还存在着一个比寄存器要大的缓存cache,而CPU在执行代码时也并不是傻瓜式的去一行一行拿着虚拟地址通过页表转换到物理内存然后再去执行代码,而是在执行代码时将代码往下的区域一同加载到cache中,这也叫做局部性原理,即使有时会出现跳转到其他代码块的情况,但是从概率上来讲,大概率执行完此行代码会继续往下执行下一行,而同一进程内不同线程之间的代码、数据、地址空间等资源都是共享的,切换线程只需要切换PCB而不需要再像切换进程一样将新进程的代码再次加载进cache中,将cache中的冷数据变成热数据。所以线程切换时相比与进程切换时要付出的成本要小很多。
三、线程的优缺点
3.1优点
3.2缺点
3.3线程vs进程
所以:
1、一个线程出问题,导致其他线程出问题,最终导致整个进程退出---线程安全问题。
2、多线程中,公共函数被多个线程同时进入---该函数被重入了。
四、多线程的使用及实操
4.1堆空间共享
如下图代码所示我们边创建线程边让之前创建好的线程去运行:
我们原本的目的是想让1 2 3 4 5这五个线程去不断的打印自己对应的名字,可是运行代码后却出现了如下的问题:
这里就暴露了线程共享数据所导致的问题:
从结果来看貌似只有5号线程在不断的运行其实不然,细看代码可以发现,我们在创建代码进行传参时,传入的是threadname的地址,所以所创建进程拿到的也是threadname的地址,但在main函数中threadname本身是在不断变化的,而创建的新线程和main函数所在的主线程是在同时运行的而每次新线程去调用threadname时都会拿到当前main函数中threadname的值。而跑到最后threadname中就是五号线程的名字,所以导致最后所有线程都在打印五号线程的名字。
所以要想解决这个问题就需要借用堆来解决:
每次给线程创建threadname时都在堆上new一块全新的空间,这样每个线程拿到的都是刚刚才开辟的不同的堆空间。