版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第10章线程控制
计算机在运行时其执行过程从大到小可以分为作业、进程和线程3个级别。线程是系统分配处理器时间资源的基本单元,或者说是进程之内独立执行的一个单元。对于操作系统而言,其调度单元是线程。Linux中的线程是轻量级线程(lightweightthread),Linux中的线程调度是由内核调度程序完成的,每个线程都有自己的ID号。与进程相比,线程所消耗的系统资源较少、创建较快、相互间的通信也比较容易。
Linux线程10.1创建线程
10.2线程属性
10.3线程等待终止10.4私有数据10.5线程同步10.6出错处理10.7思考与练习
10.810.1Linux线程线程是在共享内存空间中并发的多道执行路径,它们共享一个进程的资源。在两个普通进程(非线程)间进行切换时,内核准备从一个进程的上下文切换到另一个进程的上下文要有很大的花费,包括保存老进程CPU状态,并加载新进程的保存状态,用新进程的内存映像替换老进程的内存映像。线程也有其私有数据信息,包括:线程号(threadID):每个线程都有一个唯一的线程号与之对应。寄存器(包括程序计数器和堆栈指针)。堆栈信号掩码优先级线程私有的存储空间。多线程具有以下的优点:(1)提高应用程序的响应速度。将耗时长的操作置于一个新的线程,可以避免系统的等待。(2)使多CPU系统更有效。操作系统会保证当线程数目不大于CPU数目时不同的线程运行于不同的CPU上。(3)改善程序结构。一个长而复杂的进程可以分为多个线程,成为独立运行的部分。10.1.1线程和进程的关系Linux是支持多线程的,在一个进程内生成多个线程。一个进程可以拥有一个或多个线程。线程和进程二者之间的关系有以下几点。(1)首先,线程采用了多个线程可共享资源的设计思想。在多进程情况下,每个进程都有自己独立的地址空间,在多线程情况下,同一进程内的线程共享进程的地址空间。线程和进程的最大区别在于线程完全共享相同的地址空间,运行在同一地址上。(2)其次,由于进程地址空间独立而线程共享地址空间,所以从一个线程切换到另一线程所花费的代价比进程低。(3)再次,进程本身的信息在内存中占用的空间比线程大。因此,线程更能充分地利用内存。线程可以看作是在进程内部执行的指定序列。(4)最后,线程间的通信比进程间的通信更加方便和省时。进程间的数据空间相互独立,彼此通信要以专门的通信方式进行,通信时必须经过操作系统,而同一进程的多个线程共享数据空间,一个线程的数据可以直接提供给其他线程使用,不必进过操作系统。10.1.2线程分类内核线程Linux内核可以看作一个服务进程(管理软硬件资源,响应用户进程的种种合理以及不合理的请求)。内核需要多个执行流并行,为了防止可能的阻塞,多线程化是必要的。内核线程就是内核的分身,一个分身可以处理一件特定事情。Linux内核使用内核线程来将内核分成几个功能模块,像kswapd、kflushd等,这在处理异步事件如异步IO时特别有用。内核线程的使用是廉价的,唯一使用的资源就是内核栈和上下文切换时保存寄存器的空间。支持多线程的内核叫做多线程内核(Multi-Threadskernel)。内核线程的调度由内核负责,一个内核线程处于阻塞状态时不影响其他的内核线程,因为其是调度的基本单位。这与用户线程是不一样的。用户线程用户线程在用户空间中实现,允许多线程的程序运行时不需要特定的内核支持,内核不需要直接对用户线程进程调度,内核的调度对象和传统进程一样,还是进程本身,内核并不知道用户线程的存在。用户线程的优点如下:(1)某些线程操作的系统消耗大大减少。比如,对属于同一个进程的线程之间进行调度切换时,不需要调用系统调用,因此将减少额外的消耗,一个进程往往可以启动上千个线程。(2)用户线程的实现方式可以被定制或修改,以适应特殊应用的要求。它对于多媒体实时过程等尤其有用。另外,用户线程可以比内核线程实现方法默认情况支持更多的线程。
系统创建线程的顺序如下:当一个进程启动后,它会自动创建一个线程即主线程(mainthread)或者初始化线程(initialthread),然后利用pthread_initialize()初始化系统管理线程并且启动线程机制。线程机制启动以后需要创建线程,需要phread_create()向管理线程发送REQ_CREATE请求,调用pthread_handle_create()创建新线程。10.2创建线程
线程的创建通过函数pthread_create()来完成,该函数的原型如下:#include<pthread.h>int
pthread_create(pthread_t*thread,pthread_attr_t
*attr,void*(*start_routine)(void*),void*arg);该函数创建线程,并为其分配一个唯一的标识符pthread_t。调用者提供一个将由该线程执行的函数,该调用还可以为线程显式指定一些属性。表10-1创建线程其他系统函数函数说明pthread_t
pthread_self(void)获取本线程的线程IDint
pthread_equal(pthread_tthread1,pthread_tthread2)判断两个线程ID是否指向同一线程int
pthread_once(pthread_once_t*once_control,void(*init_routine)(void))用来保证init_routine线程函数在进程中仅执行一次。【例10-1】创建线程。设计步骤[1]在Vim中创建一个新工程文件,命名为“example10_1.c”。[2]在“example10_1.c”中创建代码如下所示。#include<stdlib.h>#include<stdio.h>#include<pthread.h>voidthread(){inti;for(i=0;i<3;i++)printf("Thisisapthread.\n");}intmain(){pthread_tid;int
i,ret;ret=pthread_create(&id,NULL,(void*)thread,NULL);if(ret!=0){printf("Createpthreaderror!\n");exit(1);}for(i=0;i<3;i++)printf("Thisisthemainprocess.\n");pthread_join(id,NULL);return(0);}用GCC编译运行程序结果如图10-1所示。图10-1创建线程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
属性的函数声明如下:int
pthread_attr_setdetachstate(pthread_attr_t*attr,int
detachstate);/*用于设置已初始化属性对象attr
中的detachstate
属性*/获取线程detachstate属性的函数声明如下:int
pthread_attr_getdetachstate(pthread_attr_t*attr,int*detachstate);/*获取detachstate
属性*/2.设置/获取guardsize
属性guardsize
属性允许应用程序指定使用此属性对象创建的线程的守护区大小。所指定的守护区大小的单位为字节。大多数系统将守护区大小向上舍入为系统可配置变量PAGESIZE的倍数。如果指定了零值,则不会创建守护区。为应用程序提供了guardsize
属性的作用为:溢出保护可能会导致系统资源浪费。如果应用程序创建大量线程,并且已知这些线程永远不会溢出其栈,则可以关闭溢出保护区。通过关闭溢出保护区,可以节省系统资源。l线程在栈上分配大型数据结构时,可能需要较大的溢出保护区来检测栈溢出。设置线程的guardsize属性的函数声明如下:int
pthread_attr_setguardsize(pthread_attr_t*attr,size_t
guardsize);/*用于设置已初始化属性对象attr中的guardsize属性值*/获取线程guardsize属性的函数声明如下:int
pthread_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属性的函数声明如下:int
pthread_attr_setschedparam(pthread_attr_t*attr,struct
sched_param
schedparam);/*用于设置已初始化属性对象attr
中的schedparam
属性*/获取线程schedparam属性的函数声明如下:int
pthread_attr_getschedparam(pthread_attr_t*attr,struct
sched_param*schedparam);/*获取schedparam属性*/4.设置/获取schedpolicy
属性schedpolicy
属性允许通过此属性对象创建的线程使用特定的调度策略,包括SCHED_OTHER(正常、非实时)、SCHED_RR(实时、轮转法)和SCHED_FIFO(实时、先入先出)等3种,缺省为SCHED_OTHER。设置线程的schedpolicy属性的函数声明如下:int
pthread_attr_setschedpolicy(pthread_attr_t*attr,intpolicy);/*用于设置已初始化属性对象attr
中的schedpolicy
属性*/获取线程schedpolicy属性的函数声明如下:int
pthread_attr_getschedpolicy(pthread_attr_t*attr,int*policy);/*获取schedpolicy
属性*/5.设置/获取inheritsched
属性inheritsched
属性选用是从创建线程中继承还是从此属性对象中获得调度策略及关联属性。inheritsched
有两种值可供选择:PTHREAD_INHERIT_SCHED:此选项指定从创建线程中继承调度策略及关联属性。如果使用attr
创建了线程,将忽略attr
参数中的调度策略和关联属性。PTHREAD_EXPLICIT_SCHED:此选项指定,从此属性对象中获得已创建线程的调度策略及关联属性。设置线程的inheritsched属性的函数声明如下:int
pthread_attr_setinheritsched(pthread_attr_t*attr,intinherit);/*用于设置已初始化属性对象attr
中的inheritsched
属性*/获取线程inheritsched属性的函数声明如下:int
pthread_attr_getinheritsched(pthread_attr_t*attr,int*inherit)/*获取inheritsched属性*/6.设置/获取scope属性scope属性用来设置创建线程的竞争范围,其合法值包括:PTHREAD_SCOPE_SYSTEM:使用此竞争范围创建的线程,将与系统中(以及同一调度域中)的其他线程竞争资源。此属性一般用于表示用户线程应该直接绑定到内核调度实体。PTHREAD_SCOPE_PROCESS:使用此竞争范围创建的线程,将直接与其进程内使用此调度竞争范围创建的其他线程争用资源。此属性一般用于表示不应绑定用户线程(不绑定到任何特定的内核调度的实体)。设置线程的scope属性的函数声明如下:int
pthread_attr_setscope(pthread_attr_t*attr,intscope);/*用于设置已初始化属性对象attr
中的contentionscope
属性*/获取线程scope属性的函数声明如下:int
pthread_attr_getscope(pthread_attr_t*attr,int*scope); /*获取scope属性*/7.设置/获取stackaddr属性此属性选项指定创建的线程将要使用的堆栈地址。应用程序全面负责这些堆栈的分配、管理和取消分配。存储分配的选项为malloc(3C)、brk(2)和mmap(2)函数。如果使用此选项,则只能使用此属性对象创建一个线程。如果创建了多个线程,它们将使用同一个堆栈。stackaddr
属性的缺省值为NULL。如果线程用户堆栈的存储不是由库分配的(也就是说,stackaddr
属性不是NULL),将忽略guardsize
属性。设置线程的stackaddr属性的函数声明如下:int
pthread_attr_setstackaddr(pthread_attr_t*attr,void*stackaddr);/*用于设置已初始化属性对象attr
中的stackaddr
属性*/获取线程stackaddr属性的函数声明如下:int
pthread_attr_getstackaddr(pthread_attr_t*attr,void**stackaddr);/*获取stackaddr属性*/8.设置/获取stacksize属性stacksize
属性用来设置创建线程的用户堆栈大小。其合法值包括:PTHREAD_STACK_MIN:此选项指定,使用此属性对象创建的线程的用户堆栈大小将使用缺省堆栈大小。此值为某个线程所需的最小堆栈大小(以字节为单位)。对于所有线程来说,这个最小值可能无法接受。
stacksize:具体的大小。定义使用此属性对象创建的线程的用户堆栈大小(以字节为单位)。此值必须大于或等于最小堆栈大小PTHREAD_STACK_MIN。设置线程的stacksize属性的函数声明如下:int
pthread_attr_setstacksize(pthread_attr_t*attr,size_t
stacksize);/*用于设置已初始化属性对象attr
中的stacksize
属性*/获取线程stacksize属性的函数声明如下:int
pthread_attr_getstacksize(pthread_attr_t*_attr,size_t*stacksize);/*获取stacksize属性*/9.设置/获取guardsize属性guardsize
属性用来警戒堆栈的大小。设置线程的guardsize属性的函数声明如下:int
pthread_attr_setguardsize(pthread_attr_t*attr,size_t
guardsize);/*用于设置已初始化属性对象attr
中的guardsize
属性*/获取线程guardsize属性的函数声明如下:int
pthread_attr_getguardsize(pthread_attr_t*_attr,size_t*guardsize);/*获取guardsize属性*/【例10-2】线程属性。设计步骤[1]在Vim中创建一个新工程文件,命名为“example10_2.c”。[2]在“example10_2.c”中创建代码如下所示。#include<stdio.h>#include<pthread.h>#include<sched.h>voidmyfunction(){inti;for(i=0;i<3;i++)printf("Thisisapthread.\n");}voidmain(){pthread_attr_t
attr;pthread_t
tid;struct
sched_param
param;int
newprio=20;pthread_attr_init(&attr);pthread_attr_getschedparam(&attr,¶m);param.sched_priority=newprio;pthread_attr_setschedparam(&attr,¶m);pthread_create(&tid,&attr,(void*)myfunction,NULL);printf("Success.\n");}
[3]用GCC编译并运行程序,其结果如图10-2所示。图10-2设置线程属性10.4线程等待终止1、线程等待线程的等待通过函数pthread_join()来实现,该函数定义为:#include<pthread.h>int
pthread_join(pthread_t_th,void**_thread_return)该函数用来等待一个线程的结束,第1个参数为被等待的线程标识符,第2个参数为一个用户定义的指针,它可以用来存储被等待线程的返回值。这个函数是一个线程阻塞函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,处于等待状态的线程资源被收回。2、线程终止新创建的线程从执行用户定义的函数处开始执行,直到出现以下情况时退出:(1)执行完成后隐式退出;(2)由线程本身显示调用pthread_exit()函数退出;它的定义如下:#include<pthread.h>voidpthread_exit(void*retval);函数pthread_exit()终止调用的线程。参数retval的值对pthread_join()函数的成功有实际意义。然而,pthread_exit()的retval必须指定,在线程退出时它才退出的数据,因此它不能够作为正在退出的线程的自动局部数据被分配。函数pthread_exit在成功调用时返回0,失败时返回-1。10.5私有数据
在多线程环境下,进程内的所有线程共享进程的数据空间,因此全局变量为所有线程共享,在程序设计中有时候需要保存线程自己的全局变量,这种特殊的变量仅在某个线程内部有效。线程私有数据采用了一种被称为一键多值的技术,即一个键对应多个值。访问数据时都是通过键值来访问,好像是对一个键值访问。操作线程私有数据的函数主要有四个:pthread_key_create()(创建一个键),pthread_setspecific()(为一个键设置线程私有数据),pthread_getspecific()(从一个键读取线程私有数据),phread_key_delete()(删除一个键)。这些函数的声明如下:#include<pthread.h>int
pthread_key_create(pthread_key_t*key,void(*destr_function)(void*));int
pthread_setspecific(pthread_key_t
key,constvoid*pointer);void*pthread_getspecific(pthread_key_tkey);int
phread_key_delete(pthread_key_tkey);
pthread_key_create():从Linux的TSD池中分配一项,将其值赋给key供以后访问使用,它的第1个参数key为指向键值的指针,第2个参数为一个函数指针,如果指针不为空,则在线程退出时将以key所关联的数据为参数调用destr_function(),释放分配的缓冲区。
pthread_setspecific():该函数将pointer的值与key相关联。用pthread_setspecific为一个键指定新的线程数据时,线程必须先释放原有的线程数据以回收空间。
pthread_getspecific():通过该函数得到与key相关联的数据。
phread_key_delete():该函数用来删除一个键,删除后,键所占用的内存将被释放。需要注意的是,键占用的内存被释放,与该键关联的线程数据所占用的内存并不被释放。因此,线程数据的释放必须在释放键之前完成。10.6线程同步如果所涉及的线程是独立的,而且异步执行,也就是说每个线程都包含了运行时自身所需要的数据或方法,而不需要外部的资源或方法,也不必关心其他线程的状态或行为。但是,有时候在进行多线程的程序设计中需要实现多个线程共享同一段代码。这时,由于线程和线程之间互相竞争CPU资源,为了解决这个问题,必须要引入同步机制。在Linux系统中提供了多种处理线程同步问题的方式,其中最常用的有互斥锁、条件变量和信号量。互斥锁通过锁机制来实现线程间的同步,互斥锁从本质上说就是一把锁,提供对共享资源的保护访问。互斥锁具有以下三个特性:原子性:如果一个线程锁定了一个互斥量,那么临界区内的操作要么全部完成,要么一个也不执行。唯一性:如果一个线程锁定了一个互斥量,那么在它解除锁定之前,没有其他线程可以锁定这个互斥量。非繁忙等待:如果一个线程已经锁定了一个互斥量,第二个线程又试图去锁定这个互斥量,则第二个线程将被挂起(不占用任何CPU资源),直到第一个线程解除对这个互斥量的锁定为止。第二个线程被唤醒并继续执行,同时锁定这个互斥量。10.6.1互斥锁表10-2互斥锁函数函数功能pthread_mutex_init初始化一个互斥锁pthread_mutex_destroy注销一个互斥锁pthread_mutex_lock加锁,如果不成功,阻塞等待pthread_mutex_unlock解锁pthread_mutex_trylock测试加锁,如果不成功则立即返回1.初始化在Linux下,线程的互斥锁在使用前,要对它进行初始化。对于静态分配的互斥锁,可以把它设置为PTHREAD_MUTEX_INITIALIZER,或者调用pthread_mutex_init()。操作语句如下:pthread_mutex_t
mutex=PTHREAD_MUTEX_INITIALIZER;对于动态分配的互斥量,在申请内存(malloc)之后,通过pthread_mutex_init()进行初始化,并且在释放内存(free)前需要调用pthread_mutex_destroy()来注销互斥锁。操作语句如下:int
pthread_mutex_init(pthread_mutex_t*restrictmutex,constpthread_mutexattr_t*restric
attr);int
pthread_mutex_destroy(pthread_mutex_t*mutex);返回值:成功则返回0,出错则返回错误编号。表10-3互斥锁的属性
属性值意义PTHREAD_MUTEX_TIMED_NP普通锁,当一个线程加锁后,其余请求锁的线程形成等待队列,解锁后按优先级获得锁PTHREAD_MUTEX_RECURSIVE_NP嵌套锁,允许一个线程对同一个锁多次加锁,并通过多次unlock解锁,如果不是同线程请求,则在解锁时重新竞争PTHREAD_MUTEX_ERRORCHECK_NP检错锁,解锁后重新竞争PTHREAD_MUTEX_ADAPTIVE_NP适应锁2.互斥操作初始化以后就可以对互斥锁进行加锁,如果互斥锁已经上了锁,调用线程会阻塞,直到被解锁。首先说一下加锁函数。原型:int
pthread_mutex_lock(pthread_mutex_t*mutex);int
pthread_mutex_trylock
(pthread_mutex_t*mutex);返回值:成功则返回0,出错则返回错误编号。其中pthread_mutex_trylock()函数是非阻塞调用模式,如果互斥锁没被锁住,pthread_mutex_trylock()函数将进行加锁,并获得对共享资源的访问权限;如果互斥锁被锁住了,pthread_mutex_trylock()函数将不会阻塞等待而直接返回忙状态。解锁函数pthread_mutex_unlock():原型:
int
pthread_mutex_unlock
(pthread_mutex_t*mutex);返回值:成功则返回0,出错则返回错误编号。3.死锁死锁主要发生在有多个依赖锁存在时,会在一个线程试图以与另一个线程相反顺序锁住互斥锁时发生。死锁是使用互斥锁时应该尽量避免的,pthread库可以跟踪这种情形,最后一个线程试图调用pthread_mutex_lock()时会失败,并返回类型为EDEADLK的错误。【例10-3】互斥锁。设计步骤[1]在Vim中创建一个新工程文件,命名为“example10_3.c”。[2]在“example10_3.c”中创建代码如下所示。
#include<stdio.h>#include<pthread.h>#include<sched.h>voidreader_function(void);voidwriter_function(void);charbuffer;int
buffer_has_item=0;pthread_mutex_t
mutex;main(void){pthread_treader;//delay.tv_nec=0;/*用默认属性初始化一个互斥锁对象*/pthread_mutex_init(&mutex,NULL);pthread_create(&reader,NULL,(void*)reader_function,NULL);writer_function();}voidwriter_function(void){printf("writer_functionbegin.\n",buffer);inti=0;while(1){/*锁定互斥锁*/pthread_mutex_lock(&mutex);if(buffer_has_item==0){buffer='a'+i;i=i+1;buffer_has_item=1;}/*打开互斥锁*/pthread_mutex_unlock(&mutex);sleep(1);}}voidreader_function(void){printf("reader_functionbegin.\n",buffer);while(1){pthread_mutex_lock(&mutex);if(buffer_has_item==1){printf("buffer=%c\n",buffer);buffer_has_item=0;}pthread_mutex_unlock(&mutex);sleep(1);}}使用GCC编译并运行程序,结果如图10-3所示。图10-3互斥锁操作结果
互斥锁一个明显的缺点是它只有两种状态:锁定和非锁定。而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。10.6.2条件变量表10-4对条件变量进行操作的函数函数功能Pthread_cond_init()初始化条件变量Pthread_cond_wait()基于条件变量阻塞,无条件等待Pthread_cond_timedwait()阻塞直到指定事件发生,计时等待Pthread_cond_signal()解除特定线程的阻塞,存在多个扥带线程时按入队顺序激活其中一个Pthread_cond_broadcast()解除所有线程的阻塞Pthread_cond_destroy()清除条件变量条件变量采用的数据类型是pthread_cond_t,在使用之前必须要进行初始化。条件变量和互斥锁一样,也有静态动态两种创建方式,静态方式使用PTHREAD_COND_INITIALIZER常量,如下:pthread_cond_t
cond=PTHREAD_COND_INITIALIZER;动态方式调用pthread_cond_init()函数,其原型为:int
pthread_cond_init(pthread_cond_t*cond,pthread_condattr_t*cond_attr)其中cond是一个指向结构pthread_cond_t的指针,cond_attr是一个指向结构pthread_condattr_t的指针。结构pthread_condattr_t是条件变量的属性结构,和互斥锁一样可以用它来设置条件变量是进程内可用还是进程间可用,默认值是PTHREAD_PROCESS_PRIVATE,即此条件变量被同一进程内的各个线程使用。注意初始化条件变量只有未被使用时才能重新初始化或被释放。函数pthread_cond_wait()使线程阻塞在一个条件变量上。它的函数原型为:int
pthread_cond_wait(pthread_cond_t*cond,pthread_mutex_t*__mutex);线程解开mutex指向的锁并被条件变量cond阻塞,直到条件被信号唤醒。通常条件表达式在互斥锁的保护下求值,如果条件表达式为假,线程基于条件表达式阻塞。当一个线程改变条件变量值时,条件变量获得一个信号,使得等待条件变量的线程退出等待状态。另一个用来阻塞线程的函数是pthread_cond_timedwait(),它的原型为:int
pthread_cond_timedwait(pthread_cond_t*cond,pthread_mutex_t*mutex,conststruct
timespec*abstime);它比函数pthread_cond_wait()多了一个时间参数,经历abstime段时间后,即使条件变量不满足,阻塞也被解除。线程可以被函数pthread_cond_signal()和函数pthread_cond_broadcast()唤醒,但是要注意的是,条件变量只是起阻塞和唤醒线程的作用,具体的判断条件还需用户给出。线程被唤醒后,它将重新检查判断条件是否满足,如果还不满足,一般说来线程应该仍阻塞在这里,被等待被下一次唤醒。这个过程一般用while语句实现。函数pthread_cond_signal()的原型为:int
pthread_cond_signal(pthread_cond_t*cond);它用来释放被阻塞在条件变量cond上的一个线程。多个线程阻塞在此条件变量上时,哪一个线程被唤醒是由线程的调度策略所决定的。函数pthread_cond_broadcast()激活所有等待线程,其函数原型为:int
pthread_cond_broadcast(pthread_cond_t*cond);当一个条件变量不再使用时,需要将其清除,清除一个条件变量使用函数pthread_cond_destroy()来实现,其函数原型为:int
pthread_cond_destroy(pthread_cond_t*cond);【例10-4】条件变量。设计步骤[1]在Vim中创建一个新工程文件,命名为“example10_4.c”。[2]在“example10_4.c”中创建代码如下所示。#include<stdlib.h>#include<stdio.h>#include<pthread.h>#include<sched.h>pthread_mutex_t
count_lock;pthread_cond_t
count_nonzero;unsignedcount;decrement_count(){printf("Createdecrement_count
pthreadsuccess!\n");pthread_mutex_lock(&count_lock);while(count==0)pthread_cond_wait(&count_nonzero,&count_lock);count=count-1;printf("Countisdecrement:count=%d\n",count);pthread_mutex_unlock(&count_lock);}increment_count(){printf("Createincrement_count
pthreadsuccess!\n");pthread_mutex_lock(&count_lock);if(count==0)pthread_cond_signal(&count_nonzero);count=count+1;printf("Countisincrement:count=%d\n",count);pthread_mutex_unlock(&count_lock);}intmain(){pthread_tid;int
i,ret;ret=pthread_create(&id,NULL,(void*)decrement_count,NULL);if(ret!=0){printf("Createdecrement_count
pthreaderror!\n");exit(1);}ret=pthread_create(&id,NULL,(void*)increment_count,NULL);if(ret!=0){printf("Createincrement_count
pthreaderror!\n");exit(1);}}用GCC编译运行程序结果如图10-4所示。图10-4条件变量例10-4的运行结果1965年,E.W.Dijkstra提出了信号量的概念,之后信号量成为操作系统实现互斥和同步的一种普遍机制。信号量是一种特殊的变量,只能取正整数值,对这些正整数只能采取两种操作,P操作(代表等待、关操作)、V操作(代表信号、开操作)。定义如下:P(sem):如果sem的值大于0,则sem减1,如果sem的值为0,则挂起该线程。V(sem):如果有其他进程因等待sem而被挂起,则让它恢复执行,如果没有线程等待sem而被挂起,则sem加上1。10.6.3信号量1、创建信号量在使用信号量之前,首先需要创建一个信号量。创建一个信号量的函数为semget(),其函数声明如下:#include<sys/types.h>#include<sys/ipc.h>#include<sys/sem.h>int
semget(key_t,int
nsems,intflag);该函数用来打开一个新的信号量集合,或者打开一个已经存在的信号量集合。其中,参数key表示所创建或打开的信号量集的键;参数nsems表示创建的信号量集中信号量的个数,此参数只在创建一个新的信号量集时才有效;参数flag表示调用函数的操作类型,也可以用来设置信号量集的访问权限。调用函数semget()的作用由参数key和flag决定,函数调用成功时返回值为信号量的引用标识符,调用失败时,返回-1。2、对信号量的操作对信号量的操作使用如下函数:#include<sys/types.h>#include<sys/ipc.h>#include<sys/sem.h>int
semop(int
semid,struct
sembuf
semoparray[],size_t
nops);参数semid是信号量集的引用id,semoparray[]是一个sembuf类型数组,sembuf结构用于指定调用semop()函数所作的操作,数组semoparray[]中的元素的个数由nops决定。sembuf结构如下:struct
sembuf{
usbort
sem_num;shortsem_op;shortsem_flag;}其中,sem_num指定要操作的信号量;sem_flag为操作标记;sem_op用于表示所执行的操作,相应取值的含义如下:sem_op>0:表示线程对资源使用完毕,交回该资源。此时信号量集的semid_ds结构的sem_base.semval将加上sem_op的值。如果此时设置了SEM_UNDO位,则信号量的调整值将减去sem_op绝对值。sem_op=0:表示进程要等待,直到sem_base.semval的值变为0。sem_op<0:表示进程希望使用资源。此时将比较sem_base.semval和sem_op的绝对值的大小。3、对信号量的控制对信号量的控制操作是通过semctl()来山实现的,函数原型如下:#include<sys/types.h>#include<sys/ipc.h>#include<sys/sem.h>int
semctl(int
semid,int
semnum,int
cmd,unionsenum
arg);其中semid为信号量集的引用标识符,semnum用于指定信号量集中某个特定的信号量,参数cmd表示调用该函数希望执行的操作,参数arg是semun联合。10.7出错处理在软件开发时需要时刻检查错误发生的各种可能,例如创建进程失败、打开文件失败等。当错误发生时,程序应当给出提示信息并进行相应的处理。在调用库函数或系统调用函数后,绝
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2024年低田空地出租合同范本
- 2024年出售附近厂房合同范本
- 2024年冲压五金加工合同范本大全
- 不同阶段的理财规划
- 粤港澳大湾区经济发展前景 2024:服务业的竞争优势与重要性
- 世界著名金融人物
- 关于雾化护理小讲课
- 2024厂房租赁合同精简范本
- 2024至2030年中国铁皮接线盒行业投资前景及策略咨询研究报告
- 2024至2030年中国香菇多糖颗粒行业投资前景及策略咨询研究报告
- 大型机械设备安全操作培训
- 管道阀门更换施工方案
- 人教版小学数学三年级上册周长【全国一等奖】
- 幼儿园小班音乐游戏活动《小老虎吃糖》教学设计【含教学反思】
- 新时代中小学思政课一体化建设探究
- 《物流机械设备》课件
- 《拼多多运营方案》课件
- 刑事受害人授权委托书范本
- 冬歇期安全施工方案
- 生物技术与生物医学
- 2024年酒店业前景与发展趋势
评论
0/150
提交评论