|
 |
栏目导栏 |
|
| |
|
|
|
|
 |
资料搜索 |
|
| |
|
|
|
|
 |
热门文章 |
|
| |
|
|
|
|
 |
最新文章 |
|
| |
|
|
|
| |
| |
|
|
|
| |
| Solaris2.4 多线程编程指南3--使用同步对象编程 |
[ 作者: Linux联盟收集 加入时间:2006-07-11 19:46:25 来自:Linux联盟收集
] | |
|
3.使用同步对象来编程 FEVLinux联盟 FEVLinux联盟 本章定义了四种可用的同步类型,并且讨论实现同步的注意事项。 FEVLinux联盟 互斥锁(mutex) FEVLinux联盟 条件变量(condition variable) FEVLinux联盟 多读单写锁(multi-read,single-write lock) FEVLinux联盟 信号量(semophore) FEVLinux联盟 进程间同步(process synchronization) FEVLinux联盟 同步原语的比较(compare primitive) FEVLinux联盟 FEVLinux联盟 同步对象是内存中的变量,你可以象访问一般的数据那样来访问它。不同进程内的线程可以通过共享内存中的同步变量来同步,即使这些线程互不可见。 FEVLinux联盟 同步变量可以放置在文件当中,可以比创建它的进程拥有更长的生命。 FEVLinux联盟 同步对象的类型包括: FEVLinux联盟 · 互斥锁 FEVLinux联盟 · 状态变量 FEVLinux联盟 · 读写锁 FEVLinux联盟 · 信号灯(信号量) FEVLinux联盟 在下面几种情况下,同步是重要的: FEVLinux联盟 · 在两个或更多个进程内的线程可以合用一个同步变量。注意,同步变量应当被一个进程初始化,在第二次初始化时,该同步变量被设置为解锁状态。 FEVLinux联盟 · 同步是唯一保证共享数据持久的办法。 FEVLinux联盟 · 一个进程可以映射一个文件并通过一个线程将其加锁,修改完成之后,该线程释放文件锁并恢复文件。在文件加锁的过程中,任何程序中的任何 线程想要加锁时都会阻塞,直至解锁; FEVLinux联盟 · 同步可以保证易变数据的安全。 FEVLinux联盟 · 同步对于简单变量也是很重要的,例如整数。在整数没有和总线对齐或 FEVLinux联盟 大于数据宽度的情况下,读写一个整数可能需要多个内存周期。虽然在SPARC系统上不会发生这样的情况,但移植程序时不能不考虑这一点; FEVLinux联盟 FEVLinux联盟 3.1互斥锁 FEVLinux联盟 FEVLinux联盟 用互斥锁可以使线程顺序执行。互斥锁通常只允许一个线程执行一个关键部分的代码,来同步线程。互斥锁也可以用来保护单线程代码。 FEVLinux联盟 Table 3-1 互斥锁函数 FEVLinux联盟 函数 操作 FEVLinux联盟 Mutex_init(3T) 初始化一个互斥锁 FEVLinux联盟 Mutext_lock(3T) 给一个互斥锁加锁 FEVLinux联盟 Mutex_trylock(3T) 加锁,如失败不阻塞 FEVLinux联盟 Mutex_unlock(3T) 解锁 FEVLinux联盟 Mutex_destroy(3T) 解除互斥状态 FEVLinux联盟 如果两个进程有共享且可写的内存,且做了相应的初始化设置后(参见mmap(2)),互斥锁可以实现进程间的线程同步。 FEVLinux联盟 互斥锁在使用前一定要初始化。 FEVLinux联盟 多线程等待一个互斥锁时,其获得互斥锁的顺序是不确定的。 FEVLinux联盟 FEVLinux联盟 3.1.1初始化一个互斥锁 FEVLinux联盟 FEVLinux联盟 mutex_init(3T) FEVLinux联盟 #include ( or #include ) FEVLinux联盟 int mutex_init(mutex_t *mp, int type, void * arg); FEVLinux联盟 用mutex_init()来初始化一个由mp指向的互斥锁。Type可以是以下值之一(arg现在先不谈)。 FEVLinux联盟 USYNC_PROCESS 互斥锁用来同步进程间的线程。 FEVLinux联盟 USYNC_THREAD 互斥锁只用来同步进程内部的线程。 FEVLinux联盟 互斥锁也可以通过分配零内存来初始化,在此种情况下应当设定USYNC_THREAD。 FEVLinux联盟 一定不会有多个线程同时初始化同一个互斥锁。一个互斥锁在使用期间一定不会被重新初始化。 FEVLinux联盟 返回值--mutex_init()在成功执行后返回零。其他值意味着错误。在以下情况发生时,函数失败并返回相关值。FEVLinux联盟 EINVAL 非法参数 FEVLinux联盟 EFAULT mp或者arg指向一个非法地址。 FEVLinux联盟 FEVLinux联盟 3.1.2给互斥锁加锁 FEVLinux联盟 FEVLinux联盟 mutex_lock(3T) FEVLinux联盟 #include (or #include ) FEVLinux联盟 int mutex_lock(mutex_t *mp); FEVLinux联盟 用mutex_lock()锁住mp指向的互斥锁。如果mutex已经被锁,当前调用线程阻塞直到互斥锁被其他线程释放(阻塞线程按照线程优先级等待)。当mutex_lock()返回,说明互斥锁已经被当前线程成功加锁。 FEVLinux联盟 返回值--mutex_lock()在成功执行后返回零。其他值意味着错误。在以下情况发生时,函数失败并返回相关值。 FEVLinux联盟 EINVAL 非法参数 FEVLinux联盟 EFAULT mp指向一个非法地址。 FEVLinux联盟 FEVLinux联盟 3.1.3加非阻塞互斥锁 FEVLinux联盟 FEVLinux联盟 mutex_trylock(3T) FEVLinux联盟 #include (or #include ) FEVLinux联盟 int mutex_trylock(mutex_t *mp); FEVLinux联盟 用mutex_trylock()来尝试给mp指向的互斥锁加锁。这个函数是mutex_lock()的非阻塞版本。当一个互斥锁已经被锁,本调用返回错误。否则,互斥锁被调用者加锁。 FEVLinux联盟 返回值--mutex_trylock()在成功执行后返回零。其他值意味着错误。在以下情况发生时,函数失败并返回相关值。 FEVLinux联盟 EINVAL 非法参数 FEVLinux联盟 EFAULT mp指向一个非法地址。 FEVLinux联盟 EBUSY mp指向的互斥锁已经被锁。 FEVLinux联盟 FEVLinux联盟 3.1.4给互斥锁解锁 FEVLinux联盟 FEVLinux联盟 mutex_unlock(3T) FEVLinux联盟 #include (or #include ) FEVLinux联盟 int mutex_unlock(mutex_t *mp); FEVLinux联盟 用mutex_unlock()给由mp指向的互斥锁解锁。互斥锁必须处于加锁状态且调用本函数的线程必须是给互斥锁加锁的线程。如果有其他线程在等待互斥锁,在等待队列头上的线程获得互斥锁并脱离阻塞状态。 FEVLinux联盟 返回值--mutex_unlock()在成功执行后返回零。其他值意味着错误。在以下情况发生时,函数失败并返回相关值。 FEVLinux联盟 EINVAL 非法参数 FEVLinux联盟 EFAULT mp指向一个非法地址。 FEVLinux联盟 FEVLinux联盟 3.1.5清除互斥锁 FEVLinux联盟 FEVLinux联盟 mutex_destroy(3T) FEVLinux联盟 #include (or #include ) FEVLinux联盟 int mutex_destroy(mutex_t *mp); FEVLinux联盟 用mutex_destroy()函数解除由mp指向的互斥锁的任何状态。储存互斥锁的内存不被释放。 FEVLinux联盟 返回值--mutex_destroy()在成功执行后返回零。其他值意味着错误。在以下情况发生时,函数失败并返回相关值。 FEVLinux联盟 EINVAL 非法参数 FEVLinux联盟 EFAULT mp指向一个非法地址。 FEVLinux联盟 FEVLinux联盟 3.1.6互斥锁代码示例 FEVLinux联盟 Code Example 3-1 Mutex Lock Example FEVLinux联盟 Mutex_t count_mutex; FEVLinux联盟 Int count; FEVLinux联盟 FEVLinux联盟 Increment_count() FEVLinux联盟 { mutex_lock(&count_mutex); FEVLinux联盟 count=count+1; FEVLinux联盟 mutex_unlock(&cout_mutex); FEVLinux联盟 } FEVLinux联盟 int get_count() FEVLinux联盟 { int c; FEVLinux联盟 mutex_lock(&count_mutex); FEVLinux联盟 c=count; FEVLinux联盟 mutex_unlock(&count_mutex); FEVLinux联盟 return(c); FEVLinux联盟 } FEVLinux联盟 在示例3-1中两个函数用互斥锁实现不同的功能,increment_count()保证对共享变量的一个原子操作(即该操作不可中断),get_count()用互斥锁保证读取count期间其值不变。 FEVLinux联盟 FEVLinux联盟 *为锁设置等级 FEVLinux联盟 FEVLinux联盟 你可能会需要同时访问两种资源。也许你在用其中一种资源时,发现需要另外一 种。就象我们在示例3-2中看到的,如果两个线程希望占有两种资源,但加互斥锁的 顺序不同,有可能会发生问题。在这个例子当中,两个线程分别给互斥锁1和2加锁, 在它们想给另外的资源加锁的时候,将会发生死锁。 FEVLinux联盟 Code Example 3-2 Deadlock FEVLinux联盟 Thread 1: FEVLinux联盟 FEVLinux联盟 Mutex_lock(&m1) FEVLinux联盟 /* use resource 1*/ FEVLinux联盟 mutex_lock(&m2); FEVLinux联盟 /* use resources 1 and 2*/ FEVLinux联盟 mutex_unlock(&m2); FEVLinux联盟 mutex_unlock(&m1); FEVLinux联盟 FEVLinux联盟 Thread 2: FEVLinux联盟 FEVLinux联盟 Mutex_lock(&m2); FEVLinux联盟 /*use resource 2*/ FEVLinux联盟 mutex_lock(&m1); FEVLinux联盟 /* use resources 1 and 2*/ FEVLinux联盟 mutex_unlock(&m1); FEVLinux联盟 mutex_unlock(&m2); FEVLinux联盟 避免这个问题的最好办法是在线程给多个互斥锁加锁时,遵循相同的顺序。这种技术的一种实现叫""锁的等级"":在逻辑上为每个锁分配一个数进行排序。 FEVLinux联盟 如果你已经拥有一个等级为I的互斥锁,你将不能给等级小于I的互斥锁加锁。 FEVLinux联盟 --------------------------------------- FEVLinux联盟 注意--lock_init可以检测这个例子当中死锁的类型。避免死锁的最好办法是采用等 FEVLinux联盟 级锁:如果对互斥锁的操作遵循一个预先定义的顺序,死锁将不会发生。 FEVLinux联盟 --------------------------------------- FEVLinux联盟 但是,这种技术并非总可以使用--有时你必须对互斥锁进行不按照预定义顺序的 操作。为了在这种情况下阻止死锁,一个线程在发现死锁用其他方法无法避免时, 必须释放已经占有的所有资源。示例3-3显示了这种方法。 FEVLinux联盟 FEVLinux联盟 Code Example 3-3 条件锁 FEVLinux联盟 Thread 1: FEVLinux联盟 Mutex_lock(&m1); FEVLinux联盟 Mutex_lock(&m2); FEVLinux联盟 Mutex_unlock(&m2); FEVLinux联盟 Mutex_unlock(&m1); FEVLinux联盟 FEVLinux联盟 Thread 2: FEVLinux联盟 For(;;){ FEVLinux联盟 Mutex_lock(&m2); FEVLinux联盟 If(mutex_trylock(&m1)==0) FEVLinux联盟 /*got it*/ FEVLinux联盟 break; FEVLinux联盟 /*didn''t get it */ FEVLinux联盟 mutex_unlock(&m1); FEVLinux联盟 } FEVLinux联盟 mutex_unlock(&m1); FEVLinux联盟 mutex_unlock(&m2); FEVLinux联盟 在上例中,线程1按照预定的顺序加锁,但线程2打乱了次序。为避免死锁,线程2必须小心操作互斥锁1:如果设置在等待互斥锁释放时阻塞,则可能导致死锁。 FEVLinux联盟 为保证上述情况不会发生,线程2调用mutex_trylock,如果互斥锁可用则用, 不可用则立刻返回失败。在这个例子当中,线程2一定要释放互斥锁2,以便线程1 可以使用互斥锁1和互斥锁2。 FEVLinux联盟 FEVLinux联盟 3.1.7锁内嵌于单链表当中 FEVLinux联盟 FEVLinux联盟 示例3-4同时占有3个锁,通过锁等级定义避免死锁。 FEVLinux联盟 Code Example 3-4 单链表结构 FEVLinux联盟 Typedef struct node1{ FEVLinux联盟 Int value; FEVLinux联盟 Struct node1 *link; FEVLinux联盟 Mutex_t lock; FEVLinux联盟 }node1_t; FEVLinux联盟 node1_t Listhead; FEVLinux联盟 此例利用单链表结构的每一个节点存储一个互斥锁。为了删除一个互斥锁,要从listhead开始搜索(它本身不会被删除),知道找到指定的节点。 FEVLinux联盟 为了保证同时删除不会发生,在访问其内容之前要先锁定节点。因为所有的搜索从listhead开始按顺序进行,所以不会出现死锁。 FEVLinux联盟 如果找到指定节点,对该节点和其前序节点加锁,因为两个节点都需要改变。因为前序节点总是首先加锁,死锁将不会发生。 FEVLinux联盟 下面C程序从单链表中删除一项。 FEVLinux联盟 Code Example 3-5 内嵌锁的单链表 FEVLinux联盟 Node1_t * delete(int value){ FEVLinux联盟 Node1_t * prev, *current; FEVLinux联盟 Prev =&listhead; FEVLinux联盟 Mutex_lock(&prev->lock); FEVLinux联盟 While((current=prev->link)!=NULL){ FEVLinux联盟 Mutex_lock(¤t->lock); FEVLinux联盟 If(current->value==value){ FEVLinux联盟 Prev->link=current->link; FEVLinux联盟 Mutex_unlock(¤t->lock); FEVLinux联盟 Mutex_unlock(&prev->lock); FEVLinux联盟 Current->link=NULL; FEVLinux联盟 Return(current); FEVLinux联盟 } FEVLinux联盟 mutex_unlock(&prev->lock); FEVLinux联盟 prev=current; FEVLinux联盟 } FEVLinux联盟 mutex_unlock(&prev->lock); FEVLinux联盟 return(NULL); FEVLinux联盟 } FEVLinux联盟 FEVLinux联盟 3.1.8内嵌在环状链表中的锁 FEVLinux联盟 FEVLinux联盟 示例3-6把前例的单链表改为环链表。环链表没有显式的表头;一个线程可以和某个节点连接,对该节点及其邻节点进行操作。等级锁在这里不容易使用,因为其链表是环状的。 FEVLinux联盟 Code Example 3-6 Circular Linked List Structure FEVLinux联盟 Typedef struct node 2 { FEVLinux联盟 Int value; FEVLinux联盟 Struct node2 *link; FEVLinux联盟 Mutex_t lock; FEVLinux联盟 } node2_t; FEVLinux联盟 FEVLinux联盟 下面的C程序给两个节点加锁,并对它们做操作。 FEVLinux联盟 Code Example 3-7 内嵌锁的环链表 FEVLinux联盟 Void Hit Neighbor(node2_t *me){ FEVLinux联盟 While(1){ FEVLinux联盟 Mutex_lock(&me->lock); FEVLinux联盟 If(mutex_lock(&me->link->lock)){ FEVLinux联盟 /* failed to get lock*/ FEVLinux联盟 mutex_unlock(&me->lock); FEVLinux联盟 continue; FEVLinux联盟 } FEVLinux联盟 break; FEVLinux联盟 } FEVLinux联盟 me->link->value += me->value; FEVLinux联盟 me->value /=2; FEVLinux联盟 mutex_unlock(&me->link->lock); FEVLinux联盟 mutex_unlock(&me->lock); FEVLinux联盟 } FEVLinux联盟 FEVLinux联盟 3.2条件变量 FEVLinux联盟 FEVLinux联盟 用条件变量来自动阻塞一个线程,直到某特殊情况发生。通常条件变量和互斥锁同时使用。 FEVLinux联盟 Table3-2 有关条件变量的函数 FEVLinux联盟 函数 操作 FEVLinux联盟 Cond_init(3T) 初始化条件变量 FEVLinux联盟 Cond_wait(3T) 基于条件变量阻塞 FEVLinux联盟 Cond_signal(3T) 解除指定线程的阻塞 FEVLinux联盟 Cond_timedwait(3T) 阻塞直到指定事件发生 FEVLinux联盟 Cond_broadcast(3T) 解除所有线程的阻塞 FEVLinux联盟 Cond_destroy(3T) 破坏条件变量 FEVLinux联盟 通过条件变量,一个线程可以自动阻塞,直到一个特定条件发生。条件的检测是在互斥锁的保护下进行的。 FEVLinux联盟 如果一个条件为假,一个线程自动阻塞,并释放等待状态改变的互斥锁。如 果另一个线程改变了条件,它发信号给关联的条件变量,唤醒一个或多个等待它 的线程,重新获得互斥锁,重新评价条件。 FEVLinux联盟 如果两进程共享可读写的内存,条件变量可以被用来实现这两进程间的线程同步。 FEVLinux联盟 使用条件变量之前要先进行初始化。而且,在有多个线程等待条件变量时,它们解除阻塞不存在确定的顺序。 FEVLinux联盟 FEVLinux联盟 3.2.1初始化条件变量 FEVLinux联盟 FEVLinux联盟 cond_init(3T) FEVLinux联盟 #include (or #include ) FEVLinux联盟 int cond_init(cond_t *cvp, int type, int arg); FEVLinux联盟 用cond_init()初始化有cvp指向的条件变量。Type可以是如下值之一(arg先 FEVLinux联盟 不谈): FEVLinux联盟 USYNC_PROCESS 条件变量可以在进程间实现线程同步; FEVLinux联盟 USYNC_THREAD 条件变量只能在进程内部对线程同步; FEVLinux联盟 条件变量可以用分配零内存来初始化,在这种情况下一定要是USYNC_THREAD。 FEVLinux联盟 多线程不能同时初始化同一个条件变量。如果一个条件变量正在使用,它不能被重新初始化。 FEVLinux联盟 返回值--cond_init()在成功执行后返回零。其他值意味着错误。在以下情况发生时,函数失败并返回相关值。 FEVLinux联盟 EINVAL 非法参数 FEVLinux联盟 EFAULT mp指向一个非法地址。 FEVLinux联盟 FEVLinux联盟 3.2.2关于条件变量阻塞 FEVLinux联盟 FEVLinux联盟 cond_wait(3T) FEVLinux联盟 #include (or #include ) FEVLinux联盟 int cond_wait(cond_t *cvp, mutex_t *mp); FEVLinux联盟 用cond_wait()释放由mp 指向的互斥锁,并且使调用线程关于cvp指向的条件 变量阻塞。被阻塞的线程可以被cond_signal(), cond_broadcast(),或者由fork() 和传递信号引起的中断唤醒。 FEVLinux联盟 与条件变量关联的条件值的改变不能从cond_wait()的返回值得出,这样的状 态必须被重新估价。 FEVLinux联盟 即使是返回错误信息,Cond_wait()通常在互斥锁被调用线程加锁后返回。 FEVLinux联盟 函数阻塞直到条件被信号唤醒。它在阻塞前自动释放互斥锁,在返回前在自动 获得它。 FEVLinux联盟 在一个典型的应用当中,一个条件表达式在互斥锁的保护下求值。如果条件表 达式为假,线程基于条件变量阻塞。当一个线程改变条件变量的值时,条件变量获 得一个信号。这使得等待该条件变量的一个或多个线程退出阻塞状态,并试图得到 互斥锁。 FEVLinux联盟 因为在被唤醒的线程的cond_wait()函数返回之前条件已经改变,导致等待的 条件在得到互斥锁之前必须重新测试。推荐的办法是在while循环中写条件检查。 FEVLinux联盟 FEVLinux联盟 Mutex_lock(); FEVLinux联盟 While(condition_is_false) FEVLinux联盟 Cond_wait(); FEVLinux联盟 Mutes_unlock(); FEVLinux联盟 如果有多个线程关于条件变量阻塞,其退出阻塞状态的顺序不确定。 FEVLinux联盟 返回值--cond_wait()在成功执行后返回零。其他值意味着错误。在以下情况发生时,函数失败并返回相关值。 FEVLinux联盟 EFAULT cvp指向一个非法地址。 FEVLinux联盟 EINTR 等待被信号或fork()中断。 FEVLinux联盟 FEVLinux联盟 3.2.3使指定线程退出阻塞状态 FEVLinux联盟 FEVLinux联盟 cond_signal(3T) FEVLinux联盟 #include (or #include ) FEVLinux联盟 int cond_signal (cond_t *cvp); FEVLinux联盟 用cond_signal()使得关于由cvp指向的条件变量阻塞的线程退出阻塞状态。在 同一个互斥锁的保护下使用cond_signal()。否则,条件变量可以在对关联条件变量 的测试和cond_wait()带来的阻塞之间获得信号,这将导致无限期的等待。 FEVLinux联盟 如果没有一个线程关于条件变量阻塞,cond_signal无效。 FEVLinux联盟 返回值--cond_signal()在成功执行后返回零。其他值意味着错误。在以下情况发生时,函数失败并返回相关值。 FEVLinux联盟 EFAULT cvp指向一个非法地址。 FEVLinux联盟 Code Example 3-8 使用cond_wait(3T)和cond_signal(3T)的例子 FEVLinux联盟 Mutex_t count_lock; FEVLinux联盟 Cond_t count_nonzero; FEVLinux联盟 Unsigned int count; FEVLinux联盟 Decrement_count() FEVLinux联盟 { FEVLinux联盟 mutex_lock(&count_lock); FEVLinux联盟 while(count==0) FEVLinux联盟 cond_wait(&count_nonzero,&count_lock); FEVLinux联盟 count=count-1; FEVLinux联盟 mutex_unlock(&count_lock); FEVLinux联盟 } FEVLinux联盟 increment_count() FEVLinux联盟 { FEVLinux联盟 mutex_lock(&count_lock); FEVLinux联盟 if(count==0) FEVLinux联盟 cond_signal(&count_nonzero); FEVLinux联盟 count=count+1; FEVLinux联盟 mutex_unlock(&count_lock); FEVLinux联盟 } FEVLinux联盟 FEVLinux联盟 3.2.4阻塞直到指定事件发生 FEVLinux联盟 FEVLinux联盟 cond_timedwait(3T) FEVLinux联盟 #include (or #include ) FEVLinux联盟 int cond_timedwait(cond_t *cvp, mutex_t *mp, FEVLinux联盟 timestruc_t *abstime); FEVLinux联盟 cond_timedwait()和cond_wait()用法相似,差别在于cond_timedwait()在经过有abstime指定的时间时不阻塞。 FEVLinux联盟 即使是返回错误,cond_timedwait()也只在给互斥锁加锁后返回。 FEVLinux联盟 Cond_timedwait()函数阻塞,直到条件变量获得信号或者经过由abstime指定 的时间。Time-out被指定为一天中的某个时间,这样条件可以在不重新计算 time-out值的情况下被有效地重新测试,???就象在示例3-9中那样。 FEVLinux联盟 返回值--cond_timedwait()在成功执行后返回零。其他值意味着错误。在以下情况发生时,函数失败并返回相关值。 FEVLinux联盟 EINVAL 由abstime 指定的时间大于应用程序启动的时间加50,000,000,或者纳秒数大于等于1,000,000,000。 FEVLinux联盟 EFAULT cvp指向一个非法地址。 FEVLinux联盟 EINTR 等待被信号或fork()中断。 FEVLinux联盟 ETIME abstime指定的时间已过。 FEVLinux联盟 Code Example 3-9 时间条件等待 FEVLinux联盟 Timestruc_t to; FEVLinux联盟 Mutex_t m; FEVLinux联盟 Cond_t c; FEVLinux联盟 Mutex_lock(&m); FEVLinux联盟 To.tv_sec=time(NULL)+TIMEOUT; FEVLinux联盟 To.tv_nsec=0; FEVLinux联盟 While (cond==FALSE){ FEVLinux联盟 Err=cond_timedwait(&c,&m,&to); FEVLinux联盟 If(err=ETIME) { FEVLinux联盟 /* TIMEOUT, do something */ FEVLinux联盟 break; FEVLinux联盟 } FEVLinux联盟 } FEVLinux联盟 mutex_unlock(&m); FEVLinux联盟 FEVLinux联盟 3.2.5使所有线程退出阻塞状态 FEVLinux联盟 FEVLinux联盟 cond_broadcast(3T) FEVLinux联盟 #include ( or #include ) FEVLinux联盟 int cond_wait(cond_t *cvp); FEVLinux联盟 用cond_broadcast()使得所有关于由cvp指向的条件变量阻塞的线程退出阻塞状态。如果没有阻塞的线程,cond_broadcast()无效。 FEVLinux联盟 这个函数唤醒所有由cond_wait()阻塞的线程。因为所有关于条件变量阻塞的线程都同时参与竞争,所以使用这个函数需要小心。 FEVLinux联盟 例如,用cond_broadcast()使得线程竞争变量资源,如示例3-10所示。 FEVLinux联盟 Code Example 3-10 条件变量广播 FEVLinux联盟 Mutex_t rsrc_lock; FEVLinux联盟 Cond_t rsrc_add; FEVLinux联盟 Unsigned int resources; FEVLinux联盟 FEVLinux联盟 Get_resources(int amount) FEVLinux联盟 { mutex_lock(&rsrc_lock); FEVLinux联盟 while(resources < amount) { FEVLinux联盟 cond_wait(&rsrc_add, &rsrc_lock); FEVLinux联盟 } FEVLinux联盟 resources-=amount; FEVLinux联盟 mutex_unlock(&rsrc_lock); FEVLinux联盟 } FEVLinux联盟 add_resources(int amount) FEVLinux联盟 { FEVLinux联盟 mutex_lock(&rsrc_lock); FEVLinux联盟 resources +=amount; FEVLinux联盟 cond_broadcast(&rsrc_add); FEVLinux联盟 mutex_unlock(&rsrc_lock); FEVLinux联盟 } FEVLinux联盟 注意,在互斥锁的保护内部,首先调用cond_broadcast()或者首先给resource增值,效果是一样的。 FEVLinux联盟 返回值--cond_broadcast()在成功执行后返回零。其他值意味着错误。在以下情况发生时,函数失败并返回相关值。 FEVLinux联盟 EFAULT cvp指向一个非法地址。 FEVLinux联盟 在互斥锁的保护下调用cond_broadcast()。否则,条件变量可能在检验关联状态和通过cond_wait()之间获得信号,这将导致永久等待。 FEVLinux联盟 FEVLinux联盟 3.2.6清除条件变量 FEVLinux联盟 FEVLinux联盟 cond_destroy(3T) FEVLinux联盟 #include ( or #include ) FEVLinux联盟 int cond_destroy(cond_t *cvp); FEVLinux联盟 使用cond_destroy() 破坏由cvp指向的条件变量的任何状态。但是储存条件变量的空间将不被释放。 FEVLinux联盟 返回值--cond_destroy()在成功执行后返回零。其他值意味着错误。在以下情况发生时,函数失败并返回相关值。 FEVLinux联盟 EFAULT cvp指向一个非法地址。 FEVLinux联盟 FEVLinux联盟 3.2.7唤醒丢失问题 FEVLinux联盟 FEVLinux联盟 在没有互斥锁保护的情况下调用cond_signal()或者cond_broadcast()会导致丢 失唤醒问题。一个唤醒丢失发生在信号或广播已经发出,但是线程即使在条件为真 时仍然关于条件变量阻塞,具体地说,这发生在调用cond_signal()时并没有获得互 斥锁的情况下。 FEVLinux联盟 如果一个线程已经作过条件检验,但是尚未调用cond_wait(),这时另外一个线 程调用cond_signal(),因为没有已被阻塞的线程,唤醒信号丢失。 FEVLinux联盟 3.2.8生产者/消费者问题 FEVLinux联盟 FEVLinux联盟 这个问题是一个标准的、著名的同时性编程问题的集合:一个有限缓冲区和两类线程,生产者和消费者,他们分别把产品放入缓冲区和从缓冲区中拿走产品。 FEVLinux联盟 一个生产者在缓冲区满时必须等待,消费者在缓冲区空时必须等待。 FEVLinux联盟 一个条件变量代表了一个等待条件的线程队列。 FEVLinux联盟 示例3-11有两个队列,一个(less)给生产者,它们等待空的位置以便放入信 息;另外一个(more)给消费者,它们等待信息放入缓冲区。这个例子也有一个互 斥锁,它是一个结构,保证同时只有一个线程可以访问缓冲区。 FEVLinux联盟 下面是缓冲区数据结构的代码。 FEVLinux联盟 Code Example 3-11 生产者/消费者问题和条件变量 FEVLinux联盟 Typedef struct{ FEVLinux联盟 Char buf[BSIZE]; FEVLinux联盟 Int occupled; FEVLinux联盟 Int nextin; FEVLinux联盟 Int nextout; FEVLinux联盟 Mutex_t mutex; FEVLinux联盟 Cond_t more; FEVLinux联盟 Cond_t less; FEVLinux联盟 }buffer_t; FEVLinux联盟 buffer_t buffer; FEVLinux联盟 如示例3-12所示,生产者用一个互斥锁保护缓冲区数据结构然后确定有足够的空 间来存放信息。如果没有,它调用cond_wait(),加入关于条件变量less阻塞的线程 队列,说明缓冲区已满。这个队列需要被信号唤醒。 FEVLinux联盟 同时,作为cond_wait()的一部分,线程释放互斥锁。等待的生产者线程依赖于 消费者线程来唤醒。当条件变量获得信号,等待less的线程队列里的第一个线程被唤 醒。但是,在线程从cond_wait()返回前,必须获得互斥锁。 FEVLinux联盟 这再次保证了线程获得对缓冲区的唯一访问权。线程一定要检测缓冲区有足够的 空间,如果有的话,它把信息放入下一个可用的位置里。 FEVLinux联盟 同时,消费者线程也许正在等待有信息放入缓冲区。这些线程等待条件变量more。 一个生产者线程,在刚刚把信息放入存储区后,调用cond_signal()来唤醒下一个等 待的消费者。(如果没有等待的消费者,这个调用无效。)最后,生产者线程释放互 斥锁,允许其他线程操作缓冲区。 FEVLinux联盟 FEVLinux联盟 Code Example 3-12 生产者/消费者问题--生产者 FEVLinux联盟 Void producer(buffer_t *b, char item) { FEVLinux联盟 Mutex_lock(&b->mutex); FEVLinux联盟 FEVLinux联盟 While ( b->occupied >= BSIZE) FEVLinux联盟 Cond_wait(&b->less, &b->mutex); FEVLinux联盟 Assert(b->occupied < BSIZE); FEVLinux联盟 b->buf(b->nextin++)=item; FEVLinux联盟 b->nextin %=BSIZE; FEVLinux联盟 b->occupied ++; FEVLinux联盟 /* now: either b->occupied < BSIZE and b->nextin is the index FEVLinux联盟 of the next empty slot in the buffer, or FEVLinux联盟 b->occupied == BSIZE and b->nextin is the index of the FEVLinux联盟 next (occupied) slot that will be emptied by a consumer FEVLinux联盟 (such as b-> == b->nextout) */ FEVLinux联盟 FEVLinux联盟 cond_signal(&b->more); FEVLinux联盟 mutex_unlock(&b->mutex); FEVLinux联盟 } FEVLinux联盟 注意assert()命令的用法;除非代码用NDEBUG方式编译,assert()在参数为真时 (非零值)不做任何操作,如果参数为假(参数为假),程序退出。 FEVLinux联盟 这种声明在多线程编程中特别有用--在失败时它们会立刻指出运行时的问题, 它们还有其他有用的特性。 FEVLinux联盟 后面说明代码可以更加称得上是声明,但它太过复杂,无法用布尔表达式来表达,所以用文字来写。??? FEVLinux联盟 声明和说明???都是不变量的实例。它们都是一些逻辑命题,在程序正常执行时不应当被证伪,除非一个线程试图改变非变量说明段的变量。??? FEVLinux联盟 不变量是一种极为有用的技术。即使它们没有在程序中写出,在分析程序中也需要把它们看成不变量。 FEVLinux联盟 生产者代码中的不变量(说明部分)在程序执行到这一段时一定为真。如果你把这段说明移到mutex_unlock()后面,它将不一定保持为真。如果将其移到紧跟着声明的后面,它仍然为真。 FEVLinux联盟 关键在于,不变量表现了一个始终为真的属性,除非一个生产者或一个消费者正 在改变缓冲区的状态。如果一个线程正在操作缓冲区(在互斥锁的保护下),它将暂 时将不变量置为假。但是,一旦线程结束对缓冲区的操作,不变量会立刻恢复为真。 FEVLinux联盟 示例3-13为消费者的代码。它的流程和生产者是对称的。 FEVLinux联盟 Code Example 3-13 生产者/消费者问题--消费者 FEVLinux联盟 Char consumer(buffer_t *b){ FEVLinux联盟 Char item; FEVLinux联盟 Mutex_lock(&b->mutex); FEVLinux联盟 While(b->occupied <=0) FEVLinux联盟 Cond_wait(&b->more, &b->mutex); FEVLinux联盟 Assert(b->occupied>0); FEVLinux联盟 Item=b->buf(b->nextout++); FEVLinux联盟 b->nextout %=BSIZE; FEVLinux联盟 b->occupied--; FEVLinux联盟 /* now: either b->occupied>0 and b->nextout is the index of FEVLinux联盟 the nexto ccupied slot in the buffer, or b->occupied==0 FEVLinux联盟 and b->nextout is the index of the next(empty) slot that FEVLinux联盟 will be filled by a producer (such as b->nextout ==b->nextin) */ FEVLinux联盟 cond_signal(&b->less); FEVLinux联盟 mutex_unlock(&b->mutex); FEVLinux联盟 return(item); FEVLinux联盟 } FEVLinux联盟 FEVLinux联盟 3.3多读单写锁 FEVLinux联盟 FEVLinux联盟 读写锁允许多个线程同时进行读操作,但一个时间至多只有一个线程进行写操作。 FEVLinux联盟 表3-3 读写锁的函数 FEVLinux联盟 函数 操作 FEVLinux联盟 rwlock_init(3T) 初始化一个读写锁 FEVLinux联盟 rw_rdlock(3T) 获得一个读锁 FEVLinux联盟 rw_tryrdlock(3T) 试图获得一个读锁 FEVLinux联盟 rw_wrlock(3T) 获得一个写锁 FEVLinux联盟 rw_trywrlock(3T) 试图获得一个写锁 FEVLinux联盟 rw_unlock(3T) 使一个读写锁退出阻塞 FEVLinux联盟 rwlock_destroy(3T) 清除读写锁状态 FEVLinux联盟 如果任何线程拥有一个读锁,其他线程也可以拥有读锁,但必须等待写锁。如 果一个线程拥有写锁,或者正在等待获得写锁,其它线程必须等待获得读锁或写锁。 FEVLinux联盟 读写锁比互斥锁要慢,但是在所保护的数据被频繁地读但并不频繁写的时候可以提高效率。 FEVLinux联盟 如果两个进程有共享的可读写的内存,可以在初始化时设置成用读写锁进行进程间的线程同步。 FEVLinux联盟 读写锁使用前一定要初始化。 FEVLinux联盟 FEVLinux联盟 3.3.1初始化一个读写锁 FEVLinux联盟 rwlock_init(3T) FEVLinux联盟 #include (or #include ) FEVLinux联盟 int rwlock_init(rwlock_t *rwlp, int type, void * arg); FEVLinux联盟 用rwlock_init()来初始化由rwlp指向的读写锁并且设置锁的状态为没有锁。 FEVLinux联盟 Type可以是如下值之一(arg现在先不谈)。 FEVLinux联盟 USYNC_PROCESS 读写锁可以实现进程间的线程同步。 FEVLinux联盟 USYNC_THREAD 读写锁只能在进程内部实现线程同步。 FEVLinux联盟 多线程不能同时初始化一个读写锁。读写锁可以通过分配零内存来初始化,在这种情况下,一定要设置USYNC_THREAD。一个读写锁在使用当中不能被其他线程重新初始化。 FEVLinux联盟 返回值--rwlock_init()在成功执行后返回零。其他值意味着错误。在以下情况发生时,函数失败并返回相关值。 FEVLinux联盟 EINVAL 非法参数。 FEVLinux联盟 EFAULT rwlp或arg指向一个非法地址。 FEVLinux联盟 FEVLinux联盟 3.3.2获得一个读锁 FEVLinux联盟 FEVLinux联盟 rw_rdlock(3T) FEVLinux联盟 #include (or #include ) FEVLinux联盟 int rw_rdlock(rwlock_t *rwlp); FEVLinux联盟 用rw_rdlock()来给一个由rwlp指向的读写锁加上读锁。如果读写锁已经被加写锁,则调用线程阻塞直到写锁被释放。否则,读锁将被成功获得。 FEVLinux联盟 返回值--rw_rdlock()在成功执行后返回零。其他值意味着错误。在以下情况发生时,函数失败并返回相关值。 FEVLinux联盟 EINVAL 非法参数。 FEVLinux联盟 EFAULT rwlp指向一个非法地址。 FEVLinux联盟 FEVLinux联盟 3.3.3试图获得一个读锁 FEVLinux联盟 FEVLinux联盟 rw_tryrdlock(3T) FEVLinux联盟 #include (or #include ) FEVLinux联盟 int rw_tryrdlock(rwlock_t *rwlp); FEVLinux联盟 试图给读写锁加读锁,如果读写锁已经被加写锁,则返回错误,而不再进入阻塞状态。否则,读锁将被成功获得。 FEVLinux联盟 返回值--rw_tryrdlock ()在成功执行后返回零。其他值意味着错误。在以下情况发生时,函数失败并返回相关值。 FEVLinux联盟 EINVAL 非法参数。 FEVLinux联盟 EFAULT rwlp指向一个非法地址。 FEVLinux联盟 EBUSY 由rwlp指向的读写锁已经被加写锁。 FEVLinux联盟 FEVLinux联盟 3.3.4获得一个写锁 FEVLinux联盟 FEVLinux联盟 rw_wrlock(3T) FEVLinux联盟 #include (or #include ) FEVLinux联盟 int rw_wrlock(rwlock_t *rwlp); FEVLinux联盟 用rw_wrlock()为由rwlp指向的读写锁加写锁。如果该读写锁已经被加读锁或写锁,则调用线程阻塞,直到所有锁被释放。一个时刻只有一个线程可以获得写锁。 FEVLinux联盟 返回值--rw_wrlock ()在成功执行后返回零。其他值意味着错误。在以下情况发生时,函数失败并返回相关值。 FEVLinux联盟 EINVAL 非法参数。 FEVLinux联盟 EFAULT rwlp指向一个非法地址。 FEVLinux联盟 FEVLinux联盟 3.3.5试图获得写锁 FEVLinux联盟 FEVLinux联盟 rw_trywrlock(3T) FEVLinux联盟 #include (or #include ) FEVLinux联盟 int rw_trywrlock(rwlock_t *rwlp); FEVLinux联盟 用rw_trywrlock()试图获得写锁,如果该读写锁已经被加读锁或写锁,它将返回错误。 FEVLinux联盟 返回值--rw_trywrlock ()在成功执行后返回零。其他值意味着错误。在以下情况发生时,函数失败并返回相关值。 FEVLinux联盟 EINVAL 非法参数。 FEVLinux联盟 EFAULT rwlp指向一个非法地址。 FEVLinux联盟 EBUSY 由rwlp指向的读写锁已被加锁。 FEVLinux联盟 FEVLinux联盟 3.3.6使一个读写锁退出阻塞状态 FEVLinux联盟 FEVLinux联盟 rw_unlock(3T) FEVLinux联盟 #include (or #include ) FEVLinux联盟 int rwlock_tryrdlock(rwlock_t *rwlp); FEVLinux联盟 用rw_unlock()来使由rwlp指向的读写锁退出阻塞状态。调用线程必须已经获得对该读写锁的读锁或写锁。如果任何其它线程在等待读写锁可用,它们当中的一个将退出阻塞状态。 FEVLinux联盟 返回值--rw_unlock ()在成功执行后返回零。其他值意味着错误。在以下情况发生时,函数失败并返回相关值。 FEVLinux联盟 EINVAL 非法参数。 FEVLinux联盟 EFAULT rwlp指向一个非法地址。 FEVLinux联盟 FEVLinux联盟 3.3.7清除读写锁 FEVLinux联盟 FEVLinux联盟 rwlock_destroy(3T) FEVLinux联盟 #include (or #include ) FEVLinux联盟 int rwlock_destroy(rwlock_t *rwlp); FEVLinux联盟 使用rwlock_destroy()来取消由rwlp指向的读写锁的状态。存储读写锁的空间不被释放。 FEVLinux联盟 返回值--rw_destroy ()在成功执行后返回零。其他值意味着错误。在以下情况发生时,函数失败并返回相关值。 FEVLinux联盟 EINVAL 非法参数。 FEVLinux联盟 EFAULT rwlp指向一个非法地址。 FEVLinux联盟 示例3-14用一个银行帐户来演示读写锁。如果一个程序允许多个线程同时进行读操作,一个时刻只有一个写操作被允许。注意get_balance()函数通过锁来保证检查和储存操作是原子操作。 FEVLinux联盟 Code Example 3-14 读/写银行帐户 FEVLinux联盟 Rwlock_t account_lock; FEVLinux联盟 Float checking_balance=100.0; FEVLinux联盟 Float saving_balance=100.0; FEVLinux联盟 … … FEVLinux联盟 rwlock_init (&account_lock, 0, NULL); FEVLinux联盟 … … FEVLinux联盟 float get_balance(){ FEVLinux联盟 float bal; FEVLinux联盟 rw_rdlock(&account_lock); FEVLinux联盟 bal=checking_balance +saving_balance; FEVLinux联盟 rw_unlock(&account_lock); FEVLinux联盟 return(bal); FEVLinux联盟 } FEVLinux联盟 void tranfer_checking_to_savings(float amount) { FEVLinux联盟 rw_wrlock(&account_lock); FEVLinux联盟 checking_balance=checking_balance - amount; FEVLinux联盟 savings_balance=savings_balance +amount; FEVLinux联盟 rw_unlock(&account_lock); FEVLinux联盟 } FEVLinux联盟 FEVLinux联盟 3.4信号量(信号灯) FEVLinux联盟 FEVLinux联盟 信号灯是E.W.Dijkstra在60年代晚期定义的程序结构。Dijkstra的模型是一个铁路上的操作:一段单线铁路在一个时刻只允许一列火车通过。 FEVLinux联盟 用一个信号灯来维护这段铁路。一列火车在进入单线铁路之前必须等待信号灯 的许可。如果一列火车进入这段轨道,信号灯改变状态,以防止其他火车进入。在 火车离开这段轨道时,必须将信号灯复原,使得其他火车得以进入。 FEVLinux联盟 在信号灯的计算机版本中,一个信号灯一般是一个整数,称之为信号量。一个 线程在被允许进行后对信号量做一个p操作。 FEVLinux联盟 P操作的字面意思是线程必须等到信号量的值为正(positive)才能继续进行, 进行前先给信号量减1。当做完相关的操作时(相当于离开铁轨),线程执行一个 v操作,即给信号量加1。这两个操作必须具有不可中断性,也叫不可分性,英文字 面为原子性(atomic),即他们不能被分成两个子操作,在子操作之间还可以插入 其它线程的其他操作,这些操作可能改变信号量。在P操作中,信号量的值在被减之 前一定要为正(使得信号量在被减1之后不会为负)。 FEVLinux联盟 在P操作或V操作当中,操作不会互相干扰。如果两个V操作要同时执行,则信号量的新值比原来大2。 FEVLinux联盟 记住P和V本身是什么意思已经不重要了,就象记住Dijkstra是荷兰人一样。但 是,如果引起了学者考证的兴趣,P代表prolagen,一个由proberen de verlagen演 变来的合成词,它的意思是""试图减""。V代表verhogen,它的意思是""增加""。这些在 Dijkstra的技术笔记EWD 74中提到过。 FEVLinux联盟 Sema_wait(3T)和sema_post(3T)分别对应Dijkstra的P和V操作, sema_trywait(3T)是P操作的一个可选的形式,在P操作不能执行时,线程不会阻塞, 而是立刻返回一个非零值。 FEVLinux联盟 有两种基本的信号量:二值信号量,其值只能是0或者1,和计数信号量,可以 是非负值。一个二值信号量在逻辑上相当于一个互斥锁。 FEVLinux联盟 然而,尽管并不强制,互斥锁应当被认为只能被拥有锁的线程释放,而""拥有信 号量的线程""这个概念是不存在的,任何线程都可以进行一个V操作 (或sema_post(3T))。 FEVLinux联盟 计数信号量的功能大概和与互斥锁合用的条件变量一样强大。在很多情况下, 采用信号量的程序比采用条件变量要简单一些(如下面的例子所示)。 FEVLinux联盟 然而,如果一个互斥锁和条件变量一起使用,有一个隐含的框架,程序的哪一 部分被保护是明显的。在信号量则不然,它可以用同时性编程当中的go to 来调用, 它更适合用于那些结构性不强的,不精确的方面。 FEVLinux联盟 FEVLinux联盟 3.4.1计数信号量 FEVLinux联盟 FEVLinux联盟 在概念上,一个信号量是一个非负整数。信号量在典型情况下用来协调资源, 信号量一般被初始化为可用资源的数量。线程在假如资源是给计数器加1,在拿走资 源时给计数器减1,操作都具有原子性。 FEVLinux联盟 如果一个信号量的值变为0,表明已无可用资源,想要给信号量减1的操作必须 等到它为正时。 FEVLinux联盟 表3-4 信号量函数 FEVLinux联盟 函数 操作 FEVLinux联盟 Sema_init(3T) 初始化信号量 FEVLinux联盟 Sema_post(3T) 增加信号量 FEVLinux联盟 Sema_wait(3T) 关于信号量阻塞 FEVLinux联盟 Sema_trywait(3T) 减少信号量 FEVLinux联盟 Sema_destroy(3T) 破坏信号量的状态 FEVLinux联盟 因为信号量不被哪个线程占有,它们可以用异步事件来通知(例如信号处理器)。 而且,因为信号量包含状态,他们可以被异步使用???,而不用象条件变量那样 一定要先获得互斥锁。 FEVLinux联盟 缺省情况下,等待信号量的多个线程退出阻塞的顺序是不确定的。 FEVLinux联盟 信号量在使用前一定要初始化。 FEVLinux联盟 FEVLinux联盟 3.4.2初始化一个信号量 FEVLinux联盟 FEVLinux联盟 sema_init(3T) FEVLinux联盟 #include (or #include ) FEVLinux联盟 int sema_init(sema_t *sp, unsigned int count, int type, void *arg); FEVLinux联盟 sema_init用count的值来初始化由sp指向的信号量。Type可以是如下值之一(arg先不谈)。 FEVLinux联盟 USYNC_PROCESS 信号量可以在进程间进行线程同步。只有一个进程需要初始化 FEVLinux联盟 信号量。Arg忽略。 FEVLinux联盟 USYNC_THREAD 信号量只能在进程内部进行线程同步。 FEVLinux联盟 多个线程不能同时初始化同一个信号量。一个信号量在使用中不能被其他线程重新初始化。 FEVLinux联盟 返回值--sema_init()在成功执行后返回零。其他值意味着错误。在以下情况发生时,函数失败并返回相关值。 FEVLinux联盟 EINVAL 非法参数。 FEVLinux联盟 EFAULT sp或arg指向一个非法地址。 FEVLinux联盟 FEVLinux联盟 3.4.3给信号量增值 FEVLinux联盟 FEVLinux联盟 sema_post(3T) FEVLinux联盟 #include (or #include ) FEVLinux联盟 int sema_destroy(sema_t *sp); FEVLinux联盟 用sema_post()给由sp指向的信号量原子地(表示其不可分性,下同)增1,如果有其它线程关于信号量阻塞,其中一个退出阻塞状态。 FEVLinux联盟 返回值--sema_post()在成功执行后返回零。其他值意味着错误。在以下情况发生时,函数失败并返回相关值。 FEVLinux联盟 EINVAL 非法参数。 FEVLinux联盟 EFAULT sp指向一个非法地址。 FEVLinux联盟 FEVLinux联盟 3.4.4关于一个信号量阻塞 FEVLinux联盟 FEVLinux联盟 sema_wait(3T) FEVLinux联盟 #include (or #include ) FEVLinux联盟 int sema_wait(sema_t *sp) FEVLinux联盟 用sema_wait()使得调用线程在由sp指向的信号量小于等于零时阻塞,在其大于零原子地对其进行减操作。 FEVLinux联盟 返回值--sema_wait()在成功执行后返回零。其他值意味着错误。在以下情况发生时,函数失败并返回相关值。 FEVLinux联盟 EINVAL 非法参数。 FEVLinux联盟 EFAULT sp指向一个非法地址。 FEVLinux联盟 EINTR 等待被信号或fork()打断。 FEVLinux联盟 FEVLinux联盟 3.4.5给信号量减值 FEVLinux联盟 FEVLinux联盟 sema_trywait(3T) FEVLinux联盟 #include (or #include ) FEVLinux联盟 int sema_trywait(sema_t *sp) FEVLinux联盟 用sema_trywait()在sp比零大时对它进行原子地减操作。是sema_wait()的非阻塞版本。 FEVLinux联盟 返回值--sema_trywait()在成功执行后返回零。其他值意味着错误。在以下情况发生时,函数失败并返回相关值。 FEVLinux联盟 EINVAL 非法参数。 FEVLinux联盟 EFAULT sp指向一个非法地址。 FEVLinux联盟 EBUSY sp 指向的值为零。 FEVLinux联盟 FEVLinux联盟 3.4.6清除信号量的状态 FEVLinux联盟 FEVLinux联盟 sema_destroy(3T) FEVLinux联盟 #include (or #include ) FEVLinux联盟 int sema_destroy(sema_t *sp) FEVLinux联盟 用sema_destroy(3T)破坏与sp指向的信号量关联的任何状态,但空间不被释放。 FEVLinux联盟 返回值--sema_destroy()在成功执行后返回零。其他值意味着错误。在以下情况发生时,函数失败并返回相关值。 FEVLinux联盟 EINVAL 非法参数。 FEVLinux联盟 EFAULT sp指向一个非法地址。 FEVLinux联盟 FEVLinux联盟 3.4.7用信号量解决生产者/消费者问题 FEVLinux联盟 FEVLinux联盟 示例3-15所示的程序与条件变量的解决方案类似;两个信号量代表空和满的缓冲区的数目,生产者线程在没有空缓冲区时阻塞,消费者在缓冲区全空时阻塞。 FEVLinux联盟 Code Example 3-15 用信号量解决的生产者/消费者问题 FEVLinux联盟 Typedef struct{ FEVLinux联盟 Char buf[BSIZE]; FEVLinux联盟 Sema_t occupied; FEVLinux联盟 Sema_t empty; FEVLinux联盟 Int nextin; FEVLinux联盟 Int nextout; FEVLinux联盟 Sema_t pmut; FEVLinux联盟 Sema_t cmut; FEVLinux联盟 } buffer_t; FEVLinux联盟 buffer_t buffer; FEVLinux联盟 sema_init(&buffer.occupied, 0, USYNC_THREAD, 0); FEVLinux联盟 sema_init(&buffer.empty, BSIZE, USYNC_THREAD, 0); FEVLinux联盟 sema_init(&buffer.pmut, 1, USYNC_THREAD, 0); FEVLinux联盟 sema_init(&buffer.cmut, 1, USYNC_THREAD, 0); FEVLinux联盟 buffer.nextin=buffer.nextout =0; FEVLinux联盟 另外一对信号量与互斥锁作用相同,用来在有多生产者和多个空缓冲区的情况下,或者是有多个消费者和多个满的缓冲区的情况下控制对缓冲区的访问。互斥锁同样可以工作,但这里主要是演示信号量的例子。 FEVLinux联盟 Code Example 3-16 生产者/消费者问题--生产者 FEVLinux联盟 Void producer(buffer_t *b, char item){ FEVLinux联盟 Sema_wait(&b->empty); FEVLinux联盟 Sema_wait(&b->pmut); FEVLinux联盟 b->buf[b->nextin]=item; FEVLinux联盟 b->nextin++; FEVLinux联盟 b->nextin %=BSIZE; FEVLinux联盟 sema_post( &b->pmut); FEVLinux联盟 sema_post(&b->occupied); FEVLinux联盟 } FEVLinux联盟 Code Example 3-17 生产者/消费者问题--消费者 FEVLinux联盟 Char consumer(buffer_t *b){ FEVLinux联盟 Char item; FEVLinux联盟 Sema_wait(&b->occupied); FEVLinux联盟 Sema_wait(&b->cmut); FEVLinux联盟 Item=b->buf[b->nextout]; FEVLinux联盟 b->nextout++; FEVLinux联盟 b->nextout %=BSIZE; FEVLinux联盟 sema_post (&b->cmut); FEVLinux联盟 sema_post(&b->empty): FEVLinux联盟 return(item); FEVLinux联盟 } FEVLinux联盟 FEVLinux联盟 3.5进程间同步 FEVLinux联盟 FEVLinux联盟 四种同步原语中的任何一种都能做进程间的同步。只要保证同步变量在共享内存 段,并且带USYNC_PROCESS参数来对其进行初始化。在这之后,对同步变量的使用和 USYNC_THREAD初始化后的线程同步是一样的。 FEVLinux联盟 FEVLinux联盟 Mutex_init(&m, USYNC_PROCESS,0); FEVLinux联盟 Rwlock_init(&rw, USYNC_PROCESS,0); FEVLinux联盟 Cond_init(&cv,USYNC_PROCESS,0); FEVLinux联盟 Sema_init(&s,count,USYNC_PROCESS,0); FEVLinux联盟 示例3-18显示了一个生产者/消费者问题,生产者和消费者在两个不同的进程里。 主函数把全零的内存段映射到它的地址空间里。注意mutex_init()和cond_init()一 定要用type=USYNC_PROCESS来初始化。 FEVLinux联盟 子进程运行消费者,父进程运行生产者。 FEVLinux联盟 此例也显示了生产者和消费者的驱动程序。生产者驱动producer_driver()简单 地从stdin中读字符并且调用生产者函数producer()。消费者驱动consumer_driver() 通过调用consumer()来读取字符,并将其写入stdout。 FEVLinux联盟 Code Example 3-18 生产者/消费者问题,用USYNC_PROCESS FEVLinux联盟 Main(){ FEVLinux联盟 Int zfd; FEVLinux联盟 Buffer_t * buffer; FEVLinux联盟 Zfd=open(""/dev/zero"", O_RDWR); FEVLinux联盟 Buffer=(buffer_t *)mmap(NULL, sizeof(buffer_t), FEVLinux联盟 PROT_READ|PROT_WRITE, MAP_SHARED, zfd, 0); FEVLinux联盟 Buffer->occupied=buffer->nextin=buffer->nextout=0; FEVLinux联盟 Mutex_init(&buffer->lock, USYNC_PROCESS,0); FEVLinux联盟 Cond_init(&buffer->less, USYNC_PROCESS, 0); FEVLinux联盟 Cond_init(&buffer->more, USYNC_PROCESS, 0); FEVLinux联盟 If(fork()==0) FEVLinux联盟 Consumer_driver(buffer); FEVLinux联盟 Else FEVLinux联盟 Producer_driver(buffer); FEVLinux联盟 } FEVLinux联盟 void producer_driver(buffer_t *b){ FEVLinux联盟 int item; FEVLinux联盟 while(1){ FEVLinux联盟 item=getchar(); FEVLinux联盟 if(item==EOF){ FEVLinux联盟 producer(b, ''''); FEVLinux联盟 break; FEVLinux联盟 } else FEVLinux联盟 producer(b, (char)item); FEVLinux联盟 } FEVLinux联盟 } FEVLinux联盟 void consumer_driver(buffer_t *b){ FEVLinux联盟 char item; FEVLinux联盟 while (1) { FEVLinux联盟 if ((item=consumer(b))=='''') FEVLinux联盟 break; FEVLinux联盟 putchar(item); FEVLinux联盟 } FEVLinux联盟 } FEVLinux联盟 一个子进程被创建出来运行消费者;父进程运行生产者。 FEVLinux联盟 FEVLinux联盟 3.6同步原语的比较 FEVLinux联盟 FEVLinux联盟 Solaris中最基本的同步原语是互斥锁。所以,在内存使用和执行时它是最 有效的。对互斥锁最基本的使用是对资源的依次访问。 FEVLinux联盟 在Solaris中效率排第二的是条件变量。条件变量的基本用法是关于一个状态 的改变而阻塞。在关于一个条件变量阻塞之前一定要先获得互斥锁,在从 cond_wait()返回且改变变量状态后一定要释放该互斥锁。 FEVLinux联盟 信号量比条件变量占用更多的内存。因为信号量是作用于状态,而不是控制,所以在一些特定的条件下它更容易使用。和锁不同,信号量没有一个所 有者。任何线程都可以给已阻塞的信号量增值。 FEVLinux联盟 读写锁是Solaris里最复杂的同步机制。这意味着它不象其他原语那样细致。一个读写锁通常用在读操作比写操作频繁的时候.FEVLinux联盟
Linux联盟收集整理 ,转贴请标明原始链接,如有任何疑问欢迎来本站Linux论坛讨论 |
|
|
|
|
|