购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

1.4 Linux线程

线程与进程类似,线程能够让应用程序更好地并发执行多个任务,也是Linux最小的任务执行单位。程序中的所有线程均可以独立执行,多个线程直接共享同一个全局内存区域,其中包括初始化数据段(Initialized Data)、未初始化数据段(Uninitialized Data),以及堆内存段(Heap Segment)。

线程之间能够方便快速地共享信息。一个线程只需将数据复制到共享(全局内存或堆)变量中,其他线程就能够直接访问到当前线程存储的信息。同时,创建线程要比创建进程快很多,甚至要快10倍以上。线程的创建之所以较快,是因为调用fork函数创建子进程时所需复制的诸多属性,在线程间的内存中本来就是共享的,所以无须采用写时复制来同步父进程的相关信息。

线程之间共享同一个进程的相关数据,从而能更好地让其他线程感知到公共资源的变化。具体包括以下数据:进程ID(process ID)、父进程ID、进程组ID、信号(signal)通知、控制终端、文件描述符等。同时每个线程都拥有一些特殊信息,方便进程对线程的管理,例如线程ID、信号掩码、线程特有数据、线程的实时调度策略(real-time scheduling policy)等。

1.4.1 Pthread简介

POSIX(Portable Operating System Interface,可移植操作系统接口)是IEEE为了在各种UNIX类系统上更好地运行软件而定义的一系列API标准的总称。Pthread是POSIX在线程领域的标准实现,定义了一整套线程创建与管理的API函数。UNIX、Linux、Mac OS X等都使用Pthread作为操作系统的线程。Windows系统也有其移植版pthreads-win32。

1.数据类型

Pthread数据类型的说明如表1-6所示。

表1-6 Pthread数据类型

2.线程操作函数

Pthread线程操作函数的说明如表1-7所示。

表1-7 Pthread线程操作函数

3.线程同步函数

Pthread提供了基于mutex互斥锁、信号量通信、超时等待等线程同步控制的能力。Pthread线程同步函数的说明如表1-8所示。

表1-8 Pthread线程同步函数

4.线程标识函数

Pthread提供了获取线程自身标识的函数,其说明如表1-9所示。

表1-9 Pthread线程标识函数

1.4.2 线程创建

pthread_create是POSIX提供的线程创建函数,如代码清单1-3所示。它通过调用Linux系统底层的do_fork函数来快速创建一个线程。

代码清单1-3 pthread_create函数

pthread_create函数有4个参数,详细信息如表1-10所示。

表1-10 pthread_create函数参数

如果pthread_create线程创建成功则返回的是0,如果值不为0说明创建失败。

pthread_attr_t是线程的属性定义,主要定义了线程的分离状态、线程栈大小、线程的调度策略等信息。pthread_attr_t的数据结构如代码清单1-4所示。在定义好线程属性对象后,可以调用pthread_attr_init函数来初始化线程属性。在线程创建结束后,可以调用pthread_attr_destroy函数来销毁线程属性对象,以防止内存泄漏。

代码清单1-4 pthread_attr_t数据结构

在上述代码中,detachstate字段用来表示线程分离状态,线程分离状态决定主线程以什么样的方式来终止自己。在默认情况下,线程是非分离状态的,主线程等待新创建的线程运行结束。而设置成分离线程状态,主线程则不会等待新创建的线程运行,它会运行结束就终止,并立即释放系统资源。可以根据业务的需要选择适当的分离状态,如将JVM的线程设置成线程分离的状态。用户可以通过pthread_attr_setdetachstate函数来动态设置线程的分离状态。

detachstae字段的值:PTHREAD_CREATE_DETACHED表示线程分离;PTHREAD _CREATE_JOINABLE表示非线程分离;stacksize表示线程的栈大小。

当系统中有很多线程时,需要减小每个线程栈的默认大小,以防止进程的地址空间不够用。同样,当线程中调用的函数调用链路很深或分配很多局部变量时,需要增大线程栈的大小,以防止栈内存溢出。在JVM里面专门定义了启动参数-Xss来设定线程栈的大小,也可以通过pthread_attr_setstacksize函数来动态设置线程的栈大小。

代码清单1-5是一个通过pthread_create创建线程的示例。该示例首先定义了task函数来打印3行简单的字符串信息,在main函数内定义了线程的栈大小、线程的分离状态等线程属性信息。然后调用pthread_create来创建一个线程,并把线程绑定上task函数执行。

代码清单1-5 创建线程示例

线程的执行结果如下:

1.4.3 线程终止

Linux提供了3种终止线程的方式:第一种是在任务的start函数里执行return语句并返回指定值;第二种是在子线程里调用pthread_exit函数,主动退出;第三种是在主线程里调用pthread_cancel函数来结束子线程。

pthread_exit函数如代码清单1-6所示。

代码清单1-6 pthread_exit函数

参数retval是void*类型的指针,可以指向任何类型的数据,它指向的数据将作为线程退出的返回值,一般用retval来表示线程异常退出的原因。如果线程不需要返回任何数据,可以将retval参数设置为NULL。

代码清单1-7是一个等待线程结束(线程主动退出)的简单示例。exitTask线程函数先打印了一行线程运行状态,然后调用pthread_exit函数来主动退出当前线程,并标明退出的原因是pthread_exit。接下来在main函数中通过pthread_create函数来创建子线程,然后通过pthread_join函数来等待子线程结束。

代码清单1-7 等待线程结束示例

然后通过thread_result来接收并打印子线程的退出原因。pthread_join执行结果如代码清单1-8所示。

代码清单1-8 pthread_join执行结果

调用pthread_cancel函数后并不会让子线程立即终止,只是提出线程取消的请求,pthread_cancel函数定义如代码清单1-9所示。子线程在取消请求(pthread_cancel)发出后会继续运行,直到到达某个取消点(Cancellation Point)。取消点是检查线程是否被取消并按照请求进行动作的位置。

代码清单1-9 pthread_cancel函数定义

pthread_cancel函数会向目标线程发送Cancel信号,但具体如何处理Cancel信号则由目标线程自己决定,目标线程可以选择忽略、立即终止,或者继续运行至取消点。默认情况下,Cancel信号是继续运行至取消点才会退出。

代码清单1-10是一个取消线程的简单示例。cancelTask线程函数先打印了一行线程运行状态,然后调用sleep函数来创造一个线程取消点。在main函数中通过pthread_create创建了子线程,然后通过pthread_cancel函数向子线程发送退出信号。

代码清单1-10 取消线程示例

子线程收到取消信号后,在下一个线程取消点上退出。代码运行结果如代码清单1-11所示。

代码清单1-11 线程取消结果 36q7Snk9B+7guv0x/4VoOIqDIYwn0MaAxdLxPhE7+8ziX0edKdAE4Uq0Y6tUSDVN

点击中间区域
呼出菜单
上一章
目录
下一章
×