1. Linux线程创建和等待
这是前面写过的创建线程的代码,现在我们把线程的ID打印看一下:
我们可以看到线程的id特别大,这是为什么呢?我们后面再说。
如果我们想自己获取自己的线程ID,该怎么办呢?
那么谁调用了这个函数,就获取谁的线程ID。
这里我们可以先写个打印函数,哪个线程对应的线程id我们打印出来。
运行结果如下:
可以看到每个线程的id不相同。
那么创建一个线程后我们也必须等待,不然可能会造成内存泄漏:
如果线程异常的该怎么办呢?
我们在这个线程5秒之后出现异常,来看运行情况:
线程异常会导致整个进程异常,线程会影响到其它线程,它的健壮性降低。
pthread_join的第二个参数value_ptr的意义是什么?
它是一个输出型参数,获取新线程退出时的退出码。
1. 线程退出的方式,return。
因为返回值是void*的,所以我们需要先强转。
在打印ret,我们也需要强转成long long,不然会出现段错误。
运行结果如下:
从运行结果可以看出:可以成功获取线程的退出码。
我们知道:进程退出有3种方式,1.代码跑完,结果正确。2.代码跑完,结果不正确。3.异常退出。
但是线程退出不需要异常,因为线程异常等于进程异常。以后我们只考虑线程正常终止。
2. 线程退出的方式,pthread_exit。
3. 线程退出的方式,pthread_cancel。
这里是获取线程的id来退出。
首先,我们让新线程死循环。
让主线程去取消新线程。
从运行结果我们可以看到:新线程也成功退出了。
为什么结果是-1呢?
因为线程退出,是OS帮我们做的。它会帮我们在线程的task_struct里面修改对应的退出码。
4. 线程退出的方式,以new的方式。
我们把new出来的地址返回。
我们也可以在外面销毁空间。
运行结果如下:
2. 线程ID
从上面的结果我们可以看出线程的ID特别大,那么线程的ID到底是什么呢?
它其实就是一个地址!我们知道:线程是一个独立的执行流,并且线程一定会在自己的运行过程中,产生临时数据(调用函数,定义局部变量等)。
线程一定需要有自己的独立的栈结构。
下面就谈一谈线程栈:
这里我们使用的线程库是用户级线程库,它是动态链接。
它的链接过程如下:
首先,我们先把自己的线程加载到物理内存,然后通过页表映射到代码区去执行。当我们执行到pthread_creat这种库函数时,发现没有,就会从磁盘中加载到物理内存,然后通过页表映射到共享区。
此时线程就会从代码区转到共享区。
在共享区执行完成后,再转到我们的代码区。
在代码区中,我们的自己的代码,调用库的代码,系统调用的代码,所有代码的执行都是在进程的地址空间当中执行的。
下面我们来看一下库和OS的关系:
线程的全部实现,并没有全部体现在OS内,而是OS提供的执行流,具体的线程结构由库来进行管理。
既然需要管理,库就会创建相关的结构体:struct thread_info,里面存了线程id和私有栈等等。那么库就会在共享区把我们的结构体信息的虚拟起始地址返回,所以pthread_t对应的就是用户级线程的控制结构体的起始地址。
Linux中,线程库用户级线程库,和内核的LWP是1:1关系。
主线程的独立栈结构,用的就是地址空间中的栈区。新线程用的栈结构,用的是库中提供的栈结构。
3. 线程的局部存储
下面讲解一个概念,线程的局部存储是什么?
我们知道:我们定义的全局变量是默认被全部线程共享的,如果我们想让某个全局变量变成私有,我们就需要定义线程的局部存储。
那么我们该如何定义?
我们把每个线程的global_val的值和地址都打印出来,并且设置一个变量看它的变化。
运行结果如下:
可以看出:每个线程的global_val的地址都是一样的,并且每个执行流都对同一个global_val进行++。
如果我们想让这个全局变量变成每个线程的私有,我们可以这样:
在变量前面加上__thread(记住是两个_)。
每个线程的global_val的地址都不一样了,并且只对自己的++。
我们知道:每个线程id在内核中都有对应的轻量级进程LWP,这个线程的id是给我们用户自己用的,我们不需要用LWP,如果我们就想要LWP,我们就需要绕过这个pthread线程库。
我们需要调用这个系统调用,但是这个我们不能直接调用,直接调用会出错。
我们需要用这个系统调用。
运行结果:
4. 分离线程
默认情况下,新创建的线程是joinable的(意思是可以等待的),线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
pthread_join的作用是:1。释放线程资源,前提是线程退出了。2.获取线程对应的退出码。
如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。
可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离:
joinable和分离是冲突的,一个线程不能既是joinable又是分离的。
举个例子:
我们在每个自己的线程里给自己分离了。
然后我们把等待退出码再打印出来看一下。
运行结果如下:
我们可以看到线程分离后,线程还是在运行。
我们在这里加一个sleep看一下:
我们可以看到,出现错误了。
这是为什么呢?
首先,我们要知道:CPU调度线程的运行是不确定的,一开始的情况是CPU先调度了主线程,然后去调度新线程,也就是说主线程先阻塞等待了,新线程还没来的及分离。
所以,我们更倾向于让主线程,分离其他线程。
新线程分离,如果主线程先退出,那么进程就退出,新线程也就退出了。一般我们分离线程,对应的main thread一般不要退出(常驻内存的进程)。
如何理解exit?
我们在线程里面exit,看一下运行情况:
任何一个线程调用exit,都表示整个进程退出。