第八章 嵌入式Linux多线程编程8.1线程基本概念8.1.1 Linux线程简介Linux中的线程是轻量级线程(lightweight thread),Linux中的线程调度是由内核调度程序完成的,每个线程有自己的ID号与进程相比,它们消耗的系统资源较少、创建较快、相互间的通信也较容易存在于同一进程中的线程会共享一些信息,这些信息包括全局变量、进程指令、大部分数据、信号处理程序和信号设置、打开的文件、当前工作的目录以及用户ID和用户组ID同时作为一个独立的线程,它们又拥有一些区别于其他线程的信息,包括线程ID、寄存器集合(如程序计数器和堆栈指针)、堆栈、错误号、信号掩码以及线程优先权 Linux线程分为两类:一是核心级支持线程在核心级实现线程时,线程的实现依赖于内核无论是在用户进程中的线程还是系统进程中的线程,它们的创建、撤销、切换都由内核实现内核感知线程的存在并对其进行控制,并且允许不同进程里的线程按照同一相对优先方法调度,这适合于发挥多处理器的并发优点当某一个线程发生阻塞时,阻塞的是该线程本身,线程所在进程中的其它线程依然可以参加线程调度在用户级实现线程时,没有核心支持的多线程进程。
因此,核心只有单线程进程概念,而多线程进程由与应用程序连接的过程库实现核心不知道线程的存在也就不能独立的调度这些线程了如果一个线程调用了一个阻塞的系统调用,进程可能被阻塞,当然其中的所有线程也同时被阻塞目前Linux众多的线程库中大部分实现的是用户级的线程,只有一些用于研究的线程库才尝试实现核心级线程系统创建线程如下:当一个进程启动后,它会自动创建一个线程即主线程(main thread)或者初始化线程(initial thread),然后就利用pthread_initialize()初始化系统管理线程并且启动线程机制线程机制启动后,要创建线程必须让pthread_create ()向管理线程发送REQ_CREATE请求, 管理线程即调用pthread_handle_create()创建新线程分配栈、设置thread属性后,以pthread_sart_thread()为函数入口调用__clone()创建并启动新线程pthread_start_thread()读取自身的进程id号存入线程描述结构中,并根据其中记录的调度方法配置调度一切准备就绪后,再调用真正的线程执行函数,并在此函数返回后调用pthread_exit()清理现场。
8.1.2 Linux线程编程基础相对进程而言,线程更加接近于执行体,它可以与同进程中的其他线程共享数据,且拥有自己的栈,拥有独立的执行序列在串行程序基础上引入线程和进程是为了提高程序的并发度,从而提高程序运行效率和响应时间现在我们通过一个简单的例子来介绍一下Linux下的多线程编程:#include <stdio.h>#include <pthread.h>void myfirstthread(void){int i;for(i=0;i<3;i++){printf("This is my thread.\n");}}int main(void){pthread_t id;int i,ret;ret=pthread_create(&id,NULL,(void *) myfirstthread,NULL);if(ret!=0){printf ("Create pthread error!\n");exit (1);}for(i=0;i<3;i++){printf("This is the main process.\n");}pthread_join(id,NULL);return (0);}要创建一个多线程程序,必须加载pthread.h文件。
线程的标识符pthread_t在头文件/usr/include/bits/pthreadtypes.h中定义:typedef unsigned long int pthread_t现在我们介绍一下多线程编程常用的几个函数:n pthread_create()函数函数pthread_create()创建一个新的线程并把它的标识符放入参数thread指向的新线程中API定义如下:#include int pthread_create(pthread_t * thread, pthread_attr_t * attr, void * (*start_routine)(void *), void * arg)第二个参数attr是用来设置线程的属性线程的属性是由函数pthread_attr_init()来生成第三个参数是新线程要执行的函数的地址第四个参数是一个void指针,可以作为任意类型的参数传给start_routine()函数;同时,start_routine()可以返回一个void *类型的返回值,而这个返回值也可以是其他类型,并由pthread_join()获取n pthread_join()函数函数pthread_join()的作用是挂起当前线程直到参数th指定的线程被终止为止。
API定义如下:#include int pthread_join (pthread_t th, void **thread_return);int pthread_detach(pthread_t th);第一个参数th为被等待的线程标识符,第二个参数为一个用户定义的指针,它可以用来存储被等待线程的返回值这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回一个线程的结束有两种途径,一种是象我们上面的例子一样,函数结束了,调用它的线程也就结束了;另一种方式是通过函数pthread_exit来实现n pthread_exit()函数 该函数调用pthread_cleanup_push()为线程注册的清除处理函数,然后结束当前线程,返回retval,父线程或其它线程可以通过函数pthread_join()来检索它API定义为:#include void pthread_exit(void * retval)n 属性控制 在上述的例子中,我们用pthread_create()函数创建了一个线程,在这个线程中,我们使用了默认参数,即将该函数的第二个参数设为NULL。
对大多数程序来说使用默认属性足够,但我们仍然需要了解一下线程的属性 属性结构为pthread_attr_t,它同样在头文件/usr/include/pthread.h中定义,属性值不能直接设置,须使用相关函数进行操作函数pthread_attr_init()的作用是初始化一个新的属性对象,函数pthread_attr_destroy()的作用是清除属性对象用户在调用这些函数之前要为属性(attr)对象分配空间include int pthread_attr_init(pthread_attr_t * attr);int pthread_attr_destroy(pthread_attr_t * attr);int pthread_attr_setdetachstate(pthread_attr_t * attr,int detachstate);int pthread_attr_getdetachstate(const pthread_attr_t * attr,int * detachstate);int pthread_attr_setschedpolicy(pthread_attr_t * attr,int policy);int pthread_attr_getschedpolicy(const pthread_attr_t * attr,int * policy);int pthread_attr_setschedparam(pthread_attr_t * attr,const struct sched_param * param);int pthread_attr_getschedparam(const pthread_attr_t * attr, struct sched_param * param);int pthread_attr_setscope(pthread_attr_t * attr,int scope);int pthread_attr_getscope(const pthread_attr_t * attr,int * scope); 关于线程的绑定,牵涉到另外一个概念:轻进程(LWP:Light Weight Process)。
轻进程可以理解为内核线程,它位于用户层和系统层之间系统对线程资源的分配、对线程的控制是通过轻进程来实现的,一个轻进程可以控制一个或多个线程默认状况下,启动多少轻进程、哪些轻进程来控制哪些线程是由系统来控制的,这种状况即称为非绑定的绑定状况下,则顾名思义,即某个线程固定的"绑"在一个轻进程之上被绑定的线程具有较高的响应速度,这是因为CPU时间片的调度是面向轻进程的,绑定的线程可以保证在需要的时候它总有一个轻进程可用通过设置被绑定的轻进程的优先级和调度级可以使得绑定的线程满足诸如实时反应之类的要求设置线程绑定状态的函数为 pthread_attr_setscope(),它有两个参数,第一个是指向属性结构的指针,第二个是绑定类型,它有两个取值: PTHREAD_SCOPE_SYSTEM(绑定的)和PTHREAD_SCOPE_PROCESS(非绑定的)线程的分离状态决定一个线程以什么样的方式来终止自己在上面的例子中,我们采用了线程的默认属性,即为非分离状态,这种情况下,原有的线程等待创建的线程结束只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源而分离线程不是这样子的,它没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。
程序员应该根据自己的需要,选择适当的分离状态设置线程分离状态的函数为 pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)第二个参数可选为PTHREAD_CREATE_DETACHED(分离线程)和 PTHREAD _CREATE_JOINABLE(非分离线程)这里要注意的一点是,如果设置一个线程为分离线程,而这个线程运行又非常快,它很可能在 pthread_create函数返回之前就终止了,它终止以后就可能将线程号和系统资源移交给其他的线程使用,这样调用pthread_create的线程就得到了错误的线程号要避免这种情况可以采取一定的同步措施,最简单的方法之一是可以在被创建的线程里调用 pthread_cond_timewait函数,让这个线程等待一会儿,留出足够的时间让函数pthread_create返回设置一段等待时间,是在多线程编程里常用的方法但是注意不要使用诸如wait()之类的函数,它们是使整个进程睡眠,并不能解决线程同步的问题线程优先级存放在结构sched_param中用函数pthread_attr_getschedparam()和函数 pthread_attr_setschedparam()进行存放,一般说来,我们总是先取优先级,对取得的值修改后再存放回去。
它们的第一个参数用于标识要操作的线程,第二个参数是线程的调度策略,第三个参数是指向调度参数的指针n 取消线程 可在当前线程中通过调用函数pthread_cancle()来取消另一个线程,该线程由参数thread指定include int pthread_cancel(pthread_t thread);int pthread_setcancelstate(int state,int * oldstate);int pthread_setcanceltype(int type,int * oldtype);void pthread_testcancel(void);线程调用pthread_setcancelstate()设置自己的取消状态,参数state是新状态,参数oldstate是一个指针,指向存放旧状态的变量(如果不为空)函数pthread_setcanceltype()修改响应取消请求的类型,响应的类型有两种:PTHREAD_CANCEL_ASYNCHRONOUS 线程被立即取消PTHREAD_CANCEL_DEFERRED 延迟取消至取消点取消点是通过调用pthread_testcancel()来创建,如果延迟取消请求挂起,那么该函数将取消当前线程。
前三个函数成功时返回0,失败时返回错误代码n pthread_cond_init()函数下列函数作用是挂起当前线程直到满足某种条件include pthread_cond_t cond=PTHREAD_COND_INITIALIZERint pthread_cond_init(pthread_cond_t * cond,pthread_condattr_t * cond_attr);int pthread_cond_signal(pthread_cond_t * cond);int pthread_cond_broadcast(pthread_cond_t * cond);int pthread_cond_wait(pthread_cond_t * cond,pthread_mutex_t * mutex);int pthread_cond_timedwait(pthread_cond_t * cond,pthread_mutex_t * mutex,const struct timespec * abstime);int pthread_cond_destroy(pthread_cond_t * cond); 函数pthread_cond_init()初始化一个pthread_cond_t类型的对象cond。
在Linux中它的第二个参数被忽略,可以简单的用PTHREAD_COND_INITIALIZER替换pthread_cond_destroy()是cond对象的析构函数,它仅检查是否还有线程在等待该条件函数pthread_cond_signal()重启动一个等待某种条件的线程pthread_cond_broadcast()重启动所有的线程函数pthread_cond_wait()对一个互斥量进行解锁,并且等待条件变量cond中的信号pthread_cond_timedwait()函数作用与pthread_cond_wait()相似,不过它只等待一段由abstime指定的时间n 互斥 互斥锁用来保证一段时间内只有一个线程在执行一段代码必要性显而易见:假设各个线程向同一个文件顺序写入数据,最后得到的结果一定是灾难性的API定义如下:#include pthread_mutex_t fastmutex = PTHREAD_MUTEX_INITIALIZER;pthread_mutex_t recmutex = PTHREAD_RECURSIVE_MUTEX_ INITIALIZER_NP;pthread_mutex_t errchkmutex= PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP;int pthread_mutex_init(pthread_mutex_t * mutex,const pthread_mutexattr_t * mutexattr);int pthread_mutex_lock(pthread_mutex_t * mutex);int pthread_mutex_trylock(pthread_mutex_t * mutex);int pthread_mutex_unlock(pthread_mutex_t * mutex);int pthread_mutex_destroy (pthread_mutex_t * mutex);函数pthread_mutex_init()和pthread_mutex_destroy()分别是互斥锁的构造函数和析构函数。
函数pthread_mutex_lock()和pthread_mutex_unlock()分别用来加锁和解锁函数pthread_mutex_trylock()和pthread_mutex_lock()相似,不同的是pthread_mutex_trylock()只有在互斥被锁住的情况下才阻塞上述函数在成功时返回0,失败时返回错误代码但pthread_mutex_init()从不失败8.2 多线程编程同步8.2.1互斥锁当在同一内存空间运行多个线程时,要注意的一个基本问题是不能让线程之间相互破坏假如两个线程要更新两个变量的值一个线程要把两个变量的值都设成0,另一个线程要把两个变量的值都设成1如果两个线程同时要同时运行,每次运行的结果可能不一样为解决该问题,pthread库提供了一种基本机制,叫互斥量(mutex)互斥量是Mutual Exclusion device的简称,它相当于一把锁,可以保证以下三点l 原子性:如果一个线程锁定了一个互斥量,那么临界区内的操作要么全部完成,要么一个也不执行l 唯一性:如果一个线程锁定了一个互斥量,那么在它解除锁定之前,没有其它线程可以锁定这个互斥量l 非繁忙等待:如果一个线程已经锁定一个互斥量,第二个线程又试图去锁定这个互斥量,则第二个线程将被挂起(不占用任何CPU资源),直到第一个线程解除对这个互斥量的锁定为止,第二个线程将被唤醒并继续执行,同时锁定这个互斥量。
我们通过下面一段代码来学习互斥锁的使用,这是一个读/写程序,它们公用一个缓冲区,并且我们假定一个缓冲区只能保存一条信息即缓冲区只有两个状态:有信息或没有信息void reader_function ( void );void writer_function ( void );char buffer;int buffer_has_item=0;pthread_mutex_t mutex;struct timespec delay;void main ( void ){ pthread_t reader; /* 定义延迟时间*/ delay.tv_sec = 2; delay.tv_nec = 0; /* 用默认属性初始化一个互斥锁对象*/ pthread_mutex_init (&mutex,NULL); pthread_create(&reader, pthread_attr_default, (void *)&reader_function), NULL); writer_function( );}void writer_function (void){ while(1){/* 锁定互斥锁*/ pthread_mutex_lock (&mutex); if (buffer_has_item==0){ buffer=make_new_item( ); buffer_has_item=1; }/* 打开互斥锁*/ pthread_mutex_unlock(&mutex); pthread_delay_np(&delay); }}void reader_function(void){ while(1){ pthread_mutex_lock(&mutex); if(buffer_has_item==1){ consume_item(buffer); buffer_has_item=0; } pthread_mutex_unlock(&mutex); pthread_delay_np(&delay); }}创建互斥量时,必须首先声明一个类型为pthread_mutex_t的变量,然后对其进行初始化,结构pthread_mutex_t为不公开的数据类型,其中包含一个系统分配的属性对象。
函数 pthread_mutex_init用来生成一个互斥锁NULL参数表明使用默认属性如果需要声明特定属性的互斥锁,须调用函数 pthread_mutexattr_init函数pthread_mutexattr_setpshared和函数 pthread_mutexattr_settype用来设置互斥锁属性前一个函数设置属性pshared,它有两个取值, PTHREAD_PROCESS_PRIVATE和PTHREAD_PROCESS_SHARED前者用来不同进程中的线程同步,后者用于同步本进程的不同线程在上面的例子中,我们使用的是默认属性PTHREAD_PROCESS_ PRIVATE后者用来设置互斥锁类型,可选的类型有PTHREAD_MUTEX_NORMAL、PTHREAD_MUTEX_ERRORCHECK、PTHREAD_MUTEX_RECURSIVE和PTHREAD _MUTEX_DEFAULT它们分别定义了不同的上锁、解锁机制,一般情况下,选用最后一个默认属性锁定一个互斥量使用函数pthread_mutex_lock(),它尝试锁定一个互斥量,如果该互斥量已经被其它线程锁定,该函数把调用自己的线程挂起,一旦该互斥量解锁,它将恢复运行并锁定该互斥量。
这个线程在做完它的事情后必须释放这个互斥量,解除锁定使用函数pthread_mutex_unlock()用完一个互斥量后必须销毁它,这时没有任何线程再需要它了,最后一个使用该互斥量的线程必须销毁它,销毁互斥量使用函数pthread_mutex_destroy():rc= pthread_mutex_destroy(&mutex);这个调用之后,mutex不能再作为一个互斥量,除非我们再初始化一次,如果在销毁互斥量后,仍然有线程试图锁定或者解锁它,那么将会从锁定或解锁函数得到一个EINVAL错误代码当一个互斥量已经被别的线程锁定后,另一个线程调用pthread_mutex_lock()函数去锁定它时,会挂起自己的线程等待这个互斥量被解锁可能存在这样一种情形,这个互斥量一直没有被解锁,等待锁定它的线程将一直被挂着,这时我们称这个线程处于饥饿状态,即它请求某个资源,但永远得不到它用户必须在程序中努力避免这种“饥饿”状态出现,pthread函数库不会自动处理这种情形但是pthread函数库可以确定另外一种状态,即“死锁” 一组线程中的所有线程都在等待被同组中另外一些线程占用的资源,这时,所有线程都因等待互斥量而披挂起,它们中任何一个都不能恢复运行,程序无法继续运行下去.这时就产生了死锁。
pthread库可以跟踪这种情形.最后一个线程试图调用pthread_mutex_lock()时会失败,并返回类型为EDEADLK的错误用户必须检查这种错误,并解决死锁问题8.2.2条件变量在8.1.1节中我们讲述了如何使用互斥锁来实现线程间数据的共享和通信,互斥锁一个明显的缺点是它只有两种状态:锁定和非锁定在某些情况下,例如:在图形用户界面程序中,一个线程读取用户输入,另一个线程处理图形输出第三个线程发送请求到服务器并处理其响应当服务器的响应到达时.处理服务器的线程必须可以通知画图形的线程,画图形的线程把相应的结果显示给用户管理用户输入的线程必须总是能响应用户,例如,允许用户取消正在由处理服务器的线程执行的耗时的操作这表明线程间必须可以相互传递信息这时候需要引入条件变量条件变量是一种可以使线程(不消耗CPU)等待某些事件发生的机制某些线程可能守候着一个条件变量,直到某个其它的线程给这个条件变量发送一个信号(即发送一个通告),这时这些线程中的一个线程就会苏醒,处理这个事件也有可能利用对条件变量的广播唤醒所有守候着这个条件变量的线程但条件变量不提供锁定,所以它必须与一个互斥量同时使用,提供访问这个环境变量时必要的锁定。
条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程这些线程将重新锁定互斥锁并重新测试条件是否满足一般说来,条件变量被用来进行线程间的同步下面通过一个例子来介绍一下条件变量: pthread_mutex_t count_mutex; pthread_cond_t count_nonzero; unsigned int count; decrement_count() { pthread_mutex_lock (&count_mutex); while(count==0) pthread_cond_wait( &count_nonzero, & count_mutex); count=count -1; pthread_mutex_unlock (&count_mutex); } increment_count(){ pthread_mutex_lock(&count_mutex); if(count==0) pthread_cond_signal(&count_nonzero); count=count+1; pthread_mutex_unlock(&count_mutex); }条件变量的结构为pthread_cond_t,创建条件变量时必须首先声明一个类型为pthread_cond_t的变量,然后对它进行初始化。
初始化可以如下所示:pthread_cond_t cond=PTHREAD_COND_INITIALIZER;但是,由于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()使线程阻塞在一个条件变量上,解锁可以使用pthread_cond_signal()(只唤醒守候着这个条件变量的一个线程)或者使用pthread_cond_broadcast()函数(唤醒守候着这个条件变量的所有线程)。
如上例所示:int rc = pthread_cond_signal(&count_nonzero);或者使用广播函数:int rc =pthread_cond_broadcast(&count_nonzero);rc在成功时返回0,失败时返回一个非0值,反映发生错误的类型(EINVAL说明函数参数不是条件变量,ENOMEM说明系统没有可用的内存)注意:发送信号成功不表明一定有线程被唤醒,可能这时候没有线程在守护该条件变量,并且这个信号会丢失,不会被使用,如果有新的线程开始守护该条件变量,那么必须要有新的信号才能唤醒它线程可以通过两个函数pthread_cond_wait()和pthread_cond_timedwait()来守候条件变量,这两个函数以一个条件变量和一个互斥量(应该在调用之前锁定)为参数,解除对互斥量的锁定,挂起线程的执行,并处于等待状态,直到条件变量接收到信号如果该线程被条件变量唤醒,守候函数再次自动锁定互斥量,并开始执行 这两个函数的唯一区别是:pthread_cond_timedwait()允许用户给定一个时间间隔,过了这个间隔,函数总是返回,返回值为ETIMEDOUT,表示在时间间隔之内条件变量没接到信号,阻塞也被解除。
而如果没有信号pthread_cond_wait()将无限期等待下去8.2.3 信号量如果我们编写的程序中使用了多线程,那么在多用户多进程系统中,需要保证只有一个线程能够对某些资源(临界资源)进行排他性访问(这些资源叫做临界资源),为防止多个程序访问一个资源引发的问题,我们需要有一种方法生成并使用一个记号,使得任意时刻只有一个线程拥有对该项资源的访问权对此Dijkstra提出了”信号量”概念信号量是一种特殊的变量,它只能取正整数值,对这些正整数只能采取两种操作:P操作(代表等待,关操作)和V操作(代表信号,开操作)P/V操作的定义如下(假设我们有一个信号量sem):P(sem):如果sem的值大于0,则sem减一;如果sem的值为0,则挂起该线程V(sem):如果有其它进程因等待sem而被挂起,则让它恢复执行,如果没有线程等待sem而被挂起,则sem加上1n 信号量集的创建与打开要使用信号量,首先必须创建一个信号量创建信号量的信号如下:#include #include #include int semget(key_t key,int nsems,int flag);函数semget()用于创建一个新的信号量集或打开—个己存在的信号量集。
其中,参数key表示所创建或打开的信号量集的键参数nsems表示创建的信号量集中信号量的个数,此参数只在创建一个新的信号量集时有效参数f1ag表示调用函数的操作类型,也可用于设置信号量集的访问权限,两者通过逻辑或表示,它低端的九个位是该信号量的权限,可以与键值IPC_CREATE作按位或操作以创建一个新的信号量,即使在设置了IPC_CREATE后给出的是一个现有信号量的键字,也并不是一个错误如果IPC_CREATE标识在函数里用不着,函数就会忽略它的作用我们可以使用IPC_CREATE和IPC_EXCL标识来创建出一个独一无二的新的信号量来,如果该信号量已经存在,则返回一个错误调用函数semget()的作用由参数key和flag决定,此函数调用成功时,返回值为信号量的引用标识符;调用失败时,返回值为-1n 对信号量的操作对信号量的操作使用如下函数:#include #include #include int semop(int semid,struct sembuf semoparray[],size_t nops);参数semid是信号量集的引用id,semoparray[]是一个sembuf类型数组,sembuf结构用于指定调用semop()函数所做的操作,数组semoparray[]中元素的个数由nops决定。
sembuf的结构如下:struct sembuf{ ushort sem_num; short sem_op; short sem_flag;}其中,sem_num指定要操作的信号量sem_op用于表示所执行的操作相应取值和含义见下sem_flag为操作标记与此函数相关的有IPC_NOWAIT和SEM_UNDO1、sem_op>0:表示线程对资源使用完毕,交回该资源此时信号量集的semid_ds结构的sem_base.semval将加上sem_op的值如果此时设置了SEM_UNDO位,则信号量的调整值将减去sem_op的绝对值2、 sem_op=0:表示进程要等待,直到sem_base.semval的值变为03、 sem_op<0:表示表示进程希望使用资源此时将比较sem_base.semval和sem_op的绝对值的大小如果sem_base.semval大于等于sem_op的绝对值,表示资源足够分配给该进程,则sem_base.semval将减去sem_op的绝对值如果此时设置了SEM_UNDO位,则信号量的调整值将加上sem_op的绝对值如果sem_base.semval小于sem_op的绝对值,表示资源不足。
如果设置了IPC_NOWAIT位,则函数出错返回,否则semid_ds结构的sem_base.semncnt加1,进程等待只至sem_base.semval大于等于sem_op的绝对值或该信号量被删除n 对信号量的控制对信号量的控制操作是通过semctl()来实现的,函数说明如下:#include #include #include int semctl(int semid,int semnum,int cmd,union semun arg); 其中semid为信号量集的引用标识符,semnum用于指定信号量集中某个特定的信号量,参数cmd表示调用该函数希望执行的操作参数arg是semun联合 union semun { int val; struct semid_ds *buf; ushort array; } cmd参数最常用的两个值是: SETVAL:用来把信号量初始化为一个已知的值,这个值在semun结构里是以val成员的面貌传递的。
它的作用是在信号量的第一次使用之前对其进行设置 IFC_RMID:删除一个已经没人使用的信号量标识码 semctl会根据cmd参数返回好几个不同的值,就SETVAL和IFC_RMID来讲,成功时返回0,失败时返回-1下面我们来看一个使用信号量的例子在这个例子中,一共有4个线程,其中两个线程负责从文件读取数据到公共的缓冲区,另两个线程从缓冲区读取数据作不同的处理(加和乘运算) /* File sem.c */ #include #include #include #include #include #define MAXSTACK 100 int stack[MAXSTACK][2];int size=0;struct sembuf lock_it;union semun options;int sem_id=semget((key_t)1234,1,0666|IPC_CREAT);options.val=0;semctl(id,0,SETVAL,options);/* 从文件1.dat读取数据,每读一次,信号量加一*/ void ReadData1(void){ FILE *fp=fopen("1.dat","r"); lock_it.sem_num=0; lock_it.sem_op=1; lock_it.sem_flg=IPC_NOWAIT; while(!feof(fp)){ fscanf(fp,"%d %d",&stack[size][0],&stack[size][1]); semop(id,&lock_it,1); ++size; } fclose(fp); }/*从文件2.dat读取数据*/ void ReadData2(void){ FILE *fp=fopen("2.dat","r"); lock_it.sem_num=0; lock_it.sem_op=1; lock_it.sem_flg=IPC_NOWAIT; while(!feof(fp)){ fscanf(fp,"%d %d",&stack[size][0],&stack[size][1]); semop(id,&lock_it,1); ++size; } fclose(fp); }/*阻塞等待缓冲区有数据,读取数据后,释放空间,继续等待*/ void HandleData1(void){ while(1){ lock_it.sem_num=0; lock_it.sem_op=-1; lock_it.sem_flg=IPC_NOWAIT; semop(id,&lock_it,1) printf("Plus:%d+%d=%d\n",stack[size][0],stack[size][1], stack[size][0]+stack[size][1]); --size; } } void HandleData2(void){ while(1){ lock_it.sem_num=0; lock_it.sem_op=-1; lock_it.sem_flg=IPC_NOWAIT; semop(id,&lock_it,1) printf("Multiply:%d*%d=%d\n",stack[size][0],stack[size][1], stack[size][0]*stack[size][1]); --size; } } int main(void){ pthread_t t1,t2,t3,t4; sem_init(&sem,0,0); pthread_create(&t1,NULL,(void *)HandleData1,NULL); pthread_create(&t2,NULL,(void *)HandleData2,NULL); pthread_create(&t3,NULL,(void *)ReadData1,NULL); pthread_create(&t4,NULL,(void *)ReadData2,NULL); /* 防止程序过早退出,让它在此无限期等待*/ pthread_join(t1,NULL); }8.3生产者-消费者问题8.3.1生产者-消费者问题简介生产者-消费者问题(Producer-Consumer)是一个著名的同步问题,它描述的是有一群生产者进程在生产消息,并将此消息提供给消费者进程去消费。
为使生产者进程和消费者进程能并发执行,在它们之间设置了一个具有n个缓冲区的缓冲池,生产者进程可将它所生产的消息放入一个缓冲区中,消费者进程可从一个缓冲区中取得一个消息消费如果缓冲池已满,则生产者进程挂起,等待消费者进程,直到有空闲缓冲区产生再唤醒生产者进程如果缓冲池为空,那么消费者进程挂起,等待生产者进程,直到有消息产生,缓冲池内有消息再唤醒消费者进程8.3.2生产者-消费者问题实例下面我们将要介绍一个实例:该实例是著名的生产者-消费者问题模型的实现,主程序中分别启动生产者线程和消费者程生产者线程不断顺序地将0 到1000 的数字写入共享的循环缓冲区,同时消费者线程不断地从共享的循环缓冲区读取数据该实验硬件基于NET-ARM3000嵌入式实验平台,软件基于PC 机操作系统redhat linux 9.0 + minicom+uClinux 开发环境该实例代码如下所示:/************************************************ * * The classic producer-consumer example. * Illustrates mutexes and conditions. * by Zou jian guo * 2003-12-22 **************************************************#include #include #include #include "pthread.h"#define BUFFER_SIZE 16/* Circular buffer of integers. */struct prodcons { int buffer[BUFFER_SIZE]; /* the actual data */ pthread_mutex_t lock; /* mutex ensuring exclusive access to buffer*/ int readpos, writepos; /* positions for reading and writing */ pthread_cond_t notempty; /* signaled when buffer is not empty */ pthread_cond_t notfull; /* signaled when buffer is not full */};/*--------------------------------------------------------*//* Initialize a buffer */void init(struct prodcons * b){ pthread_mutex_init(&b->lock, NULL); pthread_cond_init(&b->notempty, NULL); pthread_cond_init(&b->notfull, NULL); b->readpos = 0; b->writepos = 0;}/*--------------------------------------------------------*//* Store an integer in the buffer */void put(struct prodcons * b, int data){ pthread_mutex_lock(&b->lock); /* Wait until buffer is not full */ while ((b->writepos + 1) % BUFFER_SIZE == b->readpos) { printf("wait for not full\n"); pthread_cond_wait(&b->notfull, &b->lock); } /* Write the data and advance write pointer */ b->buffer[b->writepos] = data; b->writepos++; if (b->writepos >= BUFFER_SIZE) b->writepos = 0; /* Signal that the buffer is now not empty */ pthread_cond_signal(&b->notempty); pthread_mutex_unlock(&b->lock);}/*--------------------------------------------------------*//* Read and remove an integer from the buffer */int get(struct prodcons * b){ int data; pthread_mutex_lock(&b->lock); /* Wait until buffer is not empty */ while (b->writepos == b->readpos) { printf("wait for not empty\n"); pthread_cond_wait(&b->notempty, &b->lock); } /* Read the data and advance read pointer */ data = b->buffer[b->readpos]; b->readpos++; if (b->readpos >= BUFFER_SIZE) b->readpos = 0; /* Signal that the buffer is now not full */ pthread_cond_signal(&b->notfull); pthread_mutex_unlock(&b->lock); return data;}/*--------------------------------------------------------*/#define OVER (-1)struct prodcons buffer;/*--------------------------------------------------------*/void * producer(void * data){ int n; for (n = 0; n < 10。