正文样章 模板.docx
《正文样章 模板.docx》由会员分享,可在线阅读,更多相关《正文样章 模板.docx(15页珍藏版)》请在冰豆网上搜索。
正文样章模板
第10章线程控制
线程技术早在20世纪60年代就被提出,20世纪80年代中期多线程被应用到操作系统中。
目前,多线程技术已经被许多操作系统所支持,包括WindowsNT/2000和Linux。
Linux是一个多用户、多任务的操作系统。
多用户是指多个用户可以在同一时间使用计算机系统;多任务是指Linux可以同时执行几个任务,它可以在还没有执行完一个任务时又执行另一项任务。
在操作系统设计上,从进程(Process)演化出线程(Thread),最主要的目的就是更好地支持多处理器,并且减小(进程/线程)上下文切换的开销。
线程是在共享内存空间中并发的多道执行路径,它们共享一个进程的资源。
在两个普通进程(非线程)间进行切换时,内核准备从一个进程的上下文切换到另一个进程的上下文要有很大的花费,包括保存老进程CPU状态,并加载新进程的保存状态,用新进程的内存映像替换老进程的内存映像。
10.1Linux线程
根据操作系统的定义,进程是系统资源管理的最小单位,线程是计算机中程序执行的最小单位,运行时占用的系统资源较少,一个进程可以拥有多个线程。
本节介绍Linux线程的一些基本概念,包括线程和进程的关系、线程的分类等。
10.1.1线程和进程的关系
线程和进程十分相似,不同的只是线程比进程小,每个线程所占用的CPU时间是由系统分配的,也可以认为线程是操作系统分配CPU时间的基本单位,进程可以同时使用多个CPU来执行各个线程,达到最大程度的并行,以提高效率。
一个进程至少需要一个线程作为它的指令执行体。
从用户的角度看多个线程是同时执行的,从操作系统的角度看各个线程是交替执行的。
系统不停地在各个线程之间切换,每个线程只有在系统分配的时间内才能获得CPU的控制权。
&如果是在CPU多核的主机上,多个线程是可以同时运行的。
Linux是支持多线程的,在一个进程内生成多个线程。
一个进程可以拥有一个或多个线程。
线程和进程二者之间的关系有以下几点:
1)线程采用了多个线程可共享资源的设计思想。
在多进程情况下,每个进程都有自己独立的地址空间,在多线程情况下,同一进程内的线程共享进程的地址空间。
线程和进程的最大区别在于线程完全共享相同的地址空间,运行在同一地址上。
2)由于进程地址空间独立而线程共享地址空间,所以从一个线程切换到另一线程所花费的代价比进程低。
3)进程本身的信息在内存中占用的空间比线程大。
因此,线程更能充分地利用内存。
线程可以看作是在进程内部执行的指定序列。
4)线程间的通信比进程间的通信更加方便和省时。
进程间的数据空间相互独立,彼此通信要以专门的通信方式进行,通信时必须经过操作系统,而同一进程的多个线程共享数据空间,一个线程的数据可以直接提供给其他线程使用,不必进过操作系统。
线程机制支持并发程序设计技术,在多处理器上能真正保证并行处理。
而在Linux实现线程很特别,Linux把所有的线程都当作进程实现。
Linux下线程看起来就像普通进程(只是该进程和其他进程共享资源,如地址空间)。
上述机制与Microsoftwindows或是SunSolaris实现差异很大。
这些系统提供专门支持线程机制(轻量级进程)。
一个进程的组成实体可以分为两大部分:
线程集和资源集。
进程中的线程是动态的对象,代表了进程指令的执行过程。
资源,包括地址空间、打开的文件、用户信息等等,由进程内的线程共享。
线程有自己的私有数据:
程序计数器,栈空间以及寄存器。
但是由于各线程共享进程的地址空间,因此可能会导致竞争,因此对某一块有多个线程要访问的数据需要一些同步技术。
系统支持POSIX多线程接口,称为pthread(PosixThread)。
编写Linux下的多线程应用程序需要使用头文件pthread.h,连接时需要使用库libpthread.a。
10.1.2线程分类
线程是一些相关指令的离散序列,线程与其它指令序列的执行相互独立,每个程序至少包括一个线程,即主线程。
主线程负责程序的初始化工作,并且执行初始指令。
此后主线程会为执行各种不同的任务决定是分别创建其它线程还是由主线程独立承担。
不管哪种情况,每个程序至少都包含一个线程,并且每个线程都会维护自己当前的机器状态。
目前线程有用户线程和内核线程两种方法实现。
1.内核线程
Linux内核可以看作一个服务进程(管理软硬件资源,响应用户进程的种种合理以及不合理的请求)。
内核需要多个执行流并行,为了防止可能的阻塞,多线程化是必要的。
内核线程就是内核的分身,一个分身可以处理一件特定事情。
Linux内核使用内核线程来将内核分成几个功能模块,像kswapd、kflushd等,这在处理异步事件如异步IO时特别有用。
内核线程的使用是廉价的,唯一使用的资源就是内核栈和上下文切换时保存寄存器的空间。
支持多线程的内核叫做多线程内核(Multi-Threadskernel)。
内核线程的调度由内核负责,一个内核线程处于阻塞状态时不影响其他的内核线程,因为其是调度的基本单位。
这与用户线程是不一样的。
内核线程(Thread)或叫守护进程(Daemon),在操作系统中占据相当大的比例,当Linux操作系统启动以后,尤其是Xwindow也启动以后,你可以用”ps-ef”命令查看系统中的进程,这时会发现很多以”d”结尾的进程名,确切说名称显示里面加"[]"的,这些进程就是内核线程。
系统的启动是从硬件->内核->用户态进程的,pid的分配是一个往前的循环的过程,所以随系统启动的内核线程的pid往往很小。
2.用户线程
用户线程在用户空间中实现,允许多线程的程序运行时不需要特定的内核支持,内核不需要直接对用户线程进程调度,内核的调度对象和传统进程一样,还是进程本身,内核并不知道用户线程的存在。
由于Linux内核没有轻量级进程(线程)的概念,因此不能独立地对用户线程进行调度,而是由一个线程运行库来组织线程的调度,其主要工作在于在各个线程的栈之间调度。
如果一个进程中的某一个线程调用了一个阻塞的系统调用,整个进程就会被调度程序切换为等待状态,其他线程得不到运行的机会。
因此Linux使用了异步I/O机制。
用户线程的优点如下:
∙某些线程操作的系统消耗大大减少。
比如,对属于同一个进程的线程之间进行调度切换时,不需要调用系统调用,因此将减少额外的消耗,一个进程往往可以启动上千个线程。
∙用户线程的实现方式可以被定制或修改,以适应特殊应用的要求。
它对于多媒体实时过程等尤其有用。
另外,用户线程可以比内核线程实现方法默认情况支持更多的线程。
10.2创建线程
线程的创建通过函数pthread_create()来完成,该函数的声明如下:
#include
intpthread_create(pthread_t*thread,pthread_attr_t*attr,
void*(*start_routine)(void*),void*arg);
函数各参数含义如下:
∙thread:
该参数是一个指向线程标识符的指针,当线程创建成功时,用来返回创建的线程ID。
∙attr:
该参数用于指定线程的属性,NULL表示使用默认属性。
start_routine:
该参数为一个函数指针,指向线程创建后要调用的函数。
这个被线程调用的函数也称为线程函数。
∙arg:
该参数指向传递给线程函数的参数。
如果创建的thread不需要参数,则最后一个参数设置为空指针。
&线程创建成功时,pthread_create函数返回0,若不为0则说明创建线程失败。
常见的错误代码为EAGAIN和EINVAL。
pthread_create函数的第2个参数attr是一个指向pthread_attr_t结构体的指针,该结构体指明待创建线程的属性。
在头文件pthread.h中还声明了其他一些有用的系统调用,如表10-1所示。
表10-1创建线程其他系统函数
函数
说明
pthread_tpthread_self(void)
获取本线程的线程ID
intpthread_equal(pthread_tthread1,pthread_tthread2)
判断两个线程ID是否指向同一线程
intpthread_once(pthread_once_t*once_control,void(*init_routine)(void))
用来保证init_routine线程函数在进程中仅执行一次。
下面是线程创建的示例。
【例10-1】线程创建creat_thread.c。
#include
#include
#include
#include
voidthread(void)
{
inti=0;
for(i=0;i<5;i++)
{
printf(“Thisisanewthread\n”pthread_self())
if(i==1)
pthresd_exit(0);
sleep
(1);
}
}
intmain(void)
{
pthread_tthid;
printf("mainthreadis%u\n",pthread_self());
if(pthread_create(&thid,NULL,(void*)thread,NULL)!
=0)
{
printf("creatthreaderror\n");
exit
(1);
}
sleep
(1);
exit(0);
}
用GCC编译并运行程序结果如图10-1所示。
图10-1创建线程
程序首先打印出主线程的ID,然后打印新创建的线程的ID。
10.3线程属性
创建线程函数pthread_create()的第二个参数用来指定线程的属性。
线程属性主要有绑定属性、分离属性、堆栈地址、堆栈大小、优先级等。
其中系统默认的是非邦定、非分离、缺省1M的堆栈、与父进程同样级别的优先级。
(1)绑定属性
在Linux中,采用的是“一对一”的线程机制。
也就是一个用户线程对应一个内核线程。
绑定属性就是指一个用户线程固定地分配给一个内核线程,因为CPU时间片的调度是面向内核线程(轻量级进程)的,因此具有绑定属性的线程可以保证在需要的时候总有一个内核线程与之对应,而与之对应的非绑定属性就是指用户线程和内核线程的关系不是始终固定的,而是由系统来分配。
(2)分离属性
分离属性是决定线程以一个什么样的方式来终止自己。
在非分离情况下,当一个线程结束时,它多占用的线程没有得到释放,也就是没有真正的终止,需要通过pthread_join来释放资源。
而在分离属性情况下,一个线程结束时会立即释放它所占有的系统资源。
但这里有一点要注意的是,如果设置一个线程分离属性,而这个线程又运行得非常快的话,那么它很可能在pthread_create函数返回之前就终止了线程函数的运行,它终止以后就很有可能将线程号和系统资源移交给其他的线程使用,这时调用pthread_create()的线程就得到错误的线程号。
下面来看如何设置线程的属性。
1.设置/获取detachstate属性
detachstate属性表示新创建的线程与进程中其它线程是处于分离状态还是可连接状态。
其合法值包括:
∙PTHREAD_CREATE_DETACHED:
此选项使得使用attr创建的所有线程处于分离状态。
线程终止时,系统将自动回收与带有此状态的线程相关联的资源。
调用使用此属性创建的pthread_detach()或pthread_join()函数将导致错误。
∙PTHREAD_CREATE_JOINABLE:
此选项使得使用attr创建的所有线程处于可连接状态。
线程终止时,不会回收与带有此状态的线程相关联的资源。
要回收系统资源应用程序必须调用使用此属性创建的线程的pthread_detach()或pthread_join()函数。
detachstate的缺省值是PTHREAD_CREATE_JOINABLE。
设置线程的detachstate属性的函数声明如下:
intpthread_attr_setdetachstate(pthread_attr_t*attr,intdetachstate);
/*用于设置已初始化属性对象attr中的detachstate属性*/
获取线程detachstate属性的函数声明如下:
intpthread_attr_getdetachstate(pthread_attr_t*attr,int*detachstate);/*获取detachstate属性*/
2.设置/获取guardsize属性
guardsize属性允许应用程序指定使用此属性对象创建的线程的守护区大小。
所指定的守护区大小的单位为字节。
大多数系统将守护区大小向上舍入为系统可配置变量PAGESIZE的倍数。
如果指定了零值,则不会创建守护区。
为应用程序提供了guardsize属性的作用为:
溢出保护可能会导致系统资源浪费。
如果应用程序创建大量线程,并且已知这些线程永远不会溢出其栈,则可以关闭溢出保护区。
通过关闭溢出保护区,可以节省系统资源。
l线程在栈上分配大型数据结构时,可能需要较大的溢出保护区来检测栈溢出。
guardsize的缺省值为PAGESIZE字节。
PAGESIZE的实际值与实现相关,并且不可以在所有实现上使用相同值。
如果用户堆栈的存储不是由pthread库分配的,将忽略guardsize属性。
应用程序负责防止堆栈溢出。
设置线程的guardsize属性的函数声明如下:
intpthread_attr_setguardsize(pthread_attr_t*attr,size_tguardsize);
/*用于设置已初始化属性对象attr中的guardsize属性值*/
获取线程guardsize属性的函数声明如下:
intpthread_attr_getguardsize(pthread_attr_t*attr,size_t*guardsize);/*获取guardsize属性*/
3.设置/获取schedparam属性
schedparam属性的合法值因调度策略的不同而异。
对于SCHED_FIFO和SCHED_RR调度策略,只需要schedparam属性的sched_priority成员。
可以通过sched_get_priority_max()和sched_get_priority_min()获取sched_priority的合法值范围。
其他调度策略的schedparam的必要内容是不确定的。
设置线程的schedparam属性的函数声明如下:
intpthread_attr_setschedparam(pthread_attr_t*attr,structsched_paramschedparam);
/*用于设置已初始化属性对象attr中的schedparam属性*/
获取线程schedparam属性的函数声明如下:
intpthread_attr_getschedparam(pthread_attr_t*attr,structsched_param*schedparam);
/*获取schedparam属性*/
4.设置/获取schedpolicy属性
schedpolicy属性允许通过此属性对象创建的线程使用特定的调度策略,包括SCHED_OTHER(正常、非实时)、SCHED_RR(实时、轮转法)和SCHED_FIFO(实时、先入先出)等3种,缺省为SCHED_OTHER。
设置线程的schedpolicy属性的函数声明如下:
intpthread_attr_setschedpolicy(pthread_attr_t*attr,intpolicy);
/*用于设置已初始化属性对象attr中的schedpolicy属性*/
获取线程schedpolicy属性的函数声明如下:
intpthread_attr_getschedpolicy(pthread_attr_t*attr,int*policy);/*获取schedpolicy属性*/
5.设置/获取inheritsched属性
inheritsched属性选用是从创建线程中继承还是从此属性对象中获得调度策略及关联属性。
inheritsched有两种值可供选择:
∙PTHREAD_INHERIT_SCHED:
此选项指定从创建线程中继承调度策略及关联属性。
如果使用attr创建了线程,将忽略attr参数中的调度策略和关联属性。
∙PTHREAD_EXPLICIT_SCHED:
此选项指定,从此属性对象中获得已创建线程的调度策略及关联属性。
设置线程的inheritsched属性的函数声明如下:
intpthread_attr_setinheritsched(pthread_attr_t*attr,intinherit);/*用于设置已初始化属性对象attr中的inheritsched属性*/
获取线程inheritsched属性的函数声明如下:
intpthread_attr_getinheritsched(pthread_attr_t*attr,int*inherit)
/*获取inheritsched属性*/
6.设置/获取scope属性
scope属性用来设置创建线程的竞争范围,其合法值包括:
∙PTHREAD_SCOPE_SYSTEM:
使用此竞争范围创建的线程,将与系统中(以及同一调度域中)的其他线程竞争资源。
此属性一般用于表示用户线程应该直接绑定到内核调度实体。
∙PTHREAD_SCOPE_PROCESS:
使用此竞争范围创建的线程,将直接与其进程内使用此调度竞争范围创建的其他线程争用资源。
此属性一般用于表示不应绑定用户线程(不绑定到任何特定的内核调度的实体)。
设置线程的scope属性的函数声明如下:
intpthread_attr_setscope(pthread_attr_t*attr,intscope);
/*用于设置已初始化属性对象attr中的contentionscope属性*/
获取线程scope属性的函数声明如下:
intpthread_attr_getscope(pthread_attr_t*attr,int*scope);/*获取scope属性*/
7.设置/获取stackaddr属性
此属性选项指定创建的线程将要使用的堆栈地址。
应用程序全面负责这些堆栈的分配、管理和取消分配。
存储分配的选项为malloc(3C)、brk
(2)和mmap
(2)函数。
如果使用此选项,则只能使用此属性对象创建一个线程。
如果创建了多个线程,它们将使用同一个堆栈。
stackaddr属性的缺省值为NULL。
如果线程用户堆栈的存储不是由库分配的(也就是说,stackaddr属性不是NULL),将忽略guardsize属性。
设置线程的stackaddr属性的函数声明如下:
intpthread_attr_setstackaddr(pthread_attr_t*attr,void*stackaddr);
/*用于设置已初始化属性对象attr中的stackaddr属性*/
获取线程stackaddr属性的函数声明如下:
intpthread_attr_getstackaddr(pthread_attr_t*attr,void**stackaddr);/*获取stackaddr属性*/
8.设置/获取stacksize属性
stacksize属性用来设置创建线程的用户堆栈大小。
其合法值包括:
∙PTHREAD_STACK_MIN:
此选项指定,使用此属性对象创建的线程的用户堆栈大小将使用缺省堆栈大小。
此值为某个线程所需的最小堆栈大小(以字节为单位)。
对于所有线程来说,这个最小值可能无法接受。
∙stacksize:
具体的大小。
定义使用此属性对象创建的线程的用户堆栈大小(以字节为单位)。
此值必须大于或等于最小堆栈大小PTHREAD_STACK_MIN。
设置线程的stacksize属性的函数声明如下:
intpthread_attr_setstacksize(pthread_attr_t*attr,size_tstacksize);
/*用于设置已初始化属性对象attr中的stacksize属性*/
获取线程stacksize属性的函数声明如下:
intpthread_attr_getstacksize(pthread_attr_t*_attr,size_t*stacksize);/*获取stacksize属性*/
9.设置/获取guardsize属性
guardsize属性用来警戒堆栈的大小。
设置线程的guardsize属性的函数声明如下:
intpthread_attr_setguardsize(pthread_attr_t*attr,size_tguardsize);
/*用于设置已初始化属性对象attr中的guardsize属性*/
获取线程guardsize属性的函数声明如下:
intpthread_attr_getguardsize(pthread_attr_t*_attr,size_t*guardsize);/*获取guardsize属性*/
【例10-2】读取及设置线程属性。
#include
#include
#include
#include
void*thread_function(void*arg);
charmessage[]="Threadprocessing!
";
intthread_finished=0;
intmain(intargc,charargv[])
{
intres;
intstate;
pthread_ta_thread;
void*thread_result;
pthread_attr_tthread_attr;
res=pthread_attr_init(&thread_attr);
if(res!
=0)
{
perror("Itisfailed!
");
exit(EXIT_FAILURE);
}
if((res=pthread_attr_getdetachstate(&thread_attr,&state))==-1)
{
perror("pthread_attr_getdetachstate");
exit(EXIT_FAILURE);
}
else
printf("Thedefaultdetachstateis%d\n",state);
res=pthread_attr_setdetachstate(&thread_attr,PTHREAD_CREATE_DETACHED);
if(res!
=0)
{
perror("Settingdetachedattributefailed");