Solaris2.4 多线程编程指南3--使用同步对象编程
-------------
注意--lock_init可以检测这个例子当中死锁的类型。避免死锁的最好办法是采用等
级锁:如果对互斥锁的操作遵循一个预先定义的顺序,死锁将不会发生。
---------------------------------------
但是,这种技术并非总可以使用--有时你必须对互斥锁进行不按照预定义顺序的 操作。为了在这种情况下阻止死锁,一个线程在发现死锁用其他方法无法避免时, 必须释放已经占有的所有资源。示例3-3显示了这种方法。
Code Example 3-3 条件锁
Thread 1:
Mutex_lock(&m1);
Mutex_lock(&m2);
Mutex_unlock(&m2);
Mutex_unlock(&m1);
Thread 2:
For(;;){
Mutex_lock(&m2);
If(mutex_trylock(&m1)==0)
/*got it*/
break;
/*didn't get it */
mutex_unlock(&m1);
}
mutex_unlock(&m1);
mutex_unlock(&m2);
在上例中,线程1按照预定的顺序加锁,但线程2打乱了次序。为避免死锁,线程2必须小心操作互斥锁1:如果设置在等待互斥锁释放时阻塞,则可能导致死锁。
为保证上述情况不会发生,线程2调用mutex_trylock,如果互斥锁可用则用, 不可用则立刻返回失败。在这个例子当中,线程2一定要释放互斥锁2,以便线程1 可以使用互斥锁1和互斥锁2。
3.1.7锁内嵌于单链表当中
示例3-4同时占有3个锁,通过锁等级定义避免死锁。
Code Example 3-4 单链表结构
Typedef struct node1{
Int value;
Struct node1 *link;
Mutex_t lock;
}node1_t;
node1_t Listhead;
此例利用单链表结构的每一个节点存储一个互斥锁。为了删除一个互斥锁,要从listhead开始搜索(它本身不会被删除),知道找到指定的节点。
为了保证同时删除不会发生,在访问其内容之前要先锁定节点。因为所有的搜索从listhead开始按顺序进行,所以不会出现死锁。
如果找到指定节点,对该节点和其前序节点加锁,因为两个节点都需要改变。因为前序节点总是首先加锁,死锁将不会发生。
下面C程序从单链表中删除一项。
Code Example 3-5 内嵌锁的单链表
Node1_t * delete(int value){
Node1_t * prev, *current;
Prev =&listhead;
Mutex_lock(&prev->lock);
While((current=prev->link)!=NULL){
Mutex_lock(¤t->lock);
If(current->value==value){
Prev->link=current->link;
Mutex_unlock(¤t->lock);
Mutex_unlock(&prev->lock);
Current->link=NULL;
Return(current);
}
mutex_unlock(&prev->lock);
prev=current;
}
mutex_unlock(&prev->lock);
return(NULL);
}
3.1.8内嵌在环状链表中的锁
示例3-6把前例的单链表改为环链表。环链表没有显式的表头;一个线程可以和某个节点连接,对该节点及其邻节点进行操作。等级锁在这里不容易使用,因为其链表是环状的。
Code Example 3-6 Circular Linked List Structure
Typedef struct node 2 {
Int value;
Struct node2 *link;
Mutex_t lock;
} node2_t;
下面的C程序给两个节点加锁,并对它们做操作。
Code Example 3-7 内嵌锁的环链表
Void Hit Neighbor(node2_t *me){
While(1){
Mutex_lock(&me->lock);
If(mutex_lock(&me->link->lock)){
/* failed to get lock*/
mutex_unlock(&me->lock);
continue;
}
break;
}
me->link->value += me->value;
me->value /=2;
mutex_unlock(&me->link->lock);
mutex_unlock(&me->lock);
}
3.2条件变量
用条件变量来自动阻塞一个线程,直到某特殊情况发生。通常条件变量和互斥锁同时使用。
Table3-2 有关条件变量的函数
函数 操作
Cond_init(3T) 初始化条件变量
Cond_wait(3T) 基于条件变量阻塞
Cond_signal(3T) 解除指定线程的阻塞
Cond_timedwait(3T) 阻塞直到指定事件发生
Cond_broadcast(3T) 解除所有线程的阻塞
Cond_destroy(3T) 破坏条件变量
通过条件变量,一个线程可以自动阻塞,直到一个特定条件发生。条件的检测是在互斥锁的保护下进行的。
如果一个条件为假,一个线程自动阻塞,并释放等待状态改变的互斥锁。如 果另一个线程改变了条件,它发信号给关联的条件变量,唤醒一个或多个等待它 的线程,重新获得互斥锁,重新评价条件。
如果两进程共享可读写的内存,条件变量可以被用来实现这两进程间的线程同步。
使用条件变量之前要先进行初始化。而且,在有多个线程等待条件变量时,它们解除阻塞不存在确定的顺序。
3.2.1初始化条件变量
cond_init(3T)
#include(or #include )
int cond_init(cond_t *cvp, int type, int arg);
用cond_init()初始化有cvp指向的条件变量。Type可以是如下值之一(arg先
不谈):
USYNC_PROCESS 条件变量可以在进程间实现线程同步;
USYNC_THREAD 条件变量只能在进程内部对线程同步;
条件变量可以用分配零内存来初始化,在这种情况下一定要是USYNC_THREAD。
多线程不能同时初始化同一个条件变量。如果一个条件变量正在使用,它不能被重新初始化。
返回值--cond_init()在成功执行后返回零。其他值意味着错误。在以下情况发生时,函数失败并返回相关值。
EINVAL 非法参数
EFAULT mp指向一个非法地址。
3.2.2关于条件变量阻塞
cond_wait(3T)
#include(or #include )
int cond_wait(cond_t *cvp, mutex_t *mp);
用cond_wait()释放由mp 指向的互斥锁,并且使调用线程关于cvp指向的条件 变量阻塞。被阻塞的线程可以被cond_signal(), cond_broadcast(),或者由fork() 和传递信号引起的中断唤醒。
与条件变量关联的条件值的改变不能从cond_wait()的返回值得出,这样的状 态必须被重新估价。
即使是返回错误信息,Cond_wait()通常在互斥锁被调用线程加锁后返回。
函数阻塞直到条件被信号唤醒。它在阻塞前自动释放互斥锁,在返回前在自动 获得它。
在一个典型的应用当中,一个条件表达式在互斥锁的保护下求值。如果条件表 达式为假,线程基于条件变量阻塞。当一个线程改变条件变量的值时,条件变量获 得一个信号。这使得等待该条件变量的一个或多个线程退出阻塞状态,并试图得到 互斥锁。
因为在被唤醒的线程的cond_wait()函数返回之前条件已经改变,导致等待的 条件在得到互斥锁之前必须重新测试。推荐的办法是在while循环中写条件检查。
Mutex_lock();
While(condition_is_false)
Cond_wait();
Mutes_unlock();
如果有多个线程关于条件变量阻塞,其退出阻塞状态的顺序不确定。
返回值--cond_wait()在成功执行后返回零。其他值意味着错误。在以下
注意--lock_init可以检测这个例子当中死锁的类型。避免死锁的最好办法是采用等
级锁:如果对互斥锁的操作遵循一个预先定义的顺序,死锁将不会发生。
---------------------------------------
但是,这种技术并非总可以使用--有时你必须对互斥锁进行不按照预定义顺序的 操作。为了在这种情况下阻止死锁,一个线程在发现死锁用其他方法无法避免时, 必须释放已经占有的所有资源。示例3-3显示了这种方法。
Code Example 3-3 条件锁
Thread 1:
Mutex_lock(&m1);
Mutex_lock(&m2);
Mutex_unlock(&m2);
Mutex_unlock(&m1);
Thread 2:
For(;;){
Mutex_lock(&m2);
If(mutex_trylock(&m1)==0)
/*got it*/
break;
/*didn't get it */
mutex_unlock(&m1);
}
mutex_unlock(&m1);
mutex_unlock(&m2);
在上例中,线程1按照预定的顺序加锁,但线程2打乱了次序。为避免死锁,线程2必须小心操作互斥锁1:如果设置在等待互斥锁释放时阻塞,则可能导致死锁。
为保证上述情况不会发生,线程2调用mutex_trylock,如果互斥锁可用则用, 不可用则立刻返回失败。在这个例子当中,线程2一定要释放互斥锁2,以便线程1 可以使用互斥锁1和互斥锁2。
3.1.7锁内嵌于单链表当中
示例3-4同时占有3个锁,通过锁等级定义避免死锁。
Code Example 3-4 单链表结构
Typedef struct node1{
Int value;
Struct node1 *link;
Mutex_t lock;
}node1_t;
node1_t Listhead;
此例利用单链表结构的每一个节点存储一个互斥锁。为了删除一个互斥锁,要从listhead开始搜索(它本身不会被删除),知道找到指定的节点。
为了保证同时删除不会发生,在访问其内容之前要先锁定节点。因为所有的搜索从listhead开始按顺序进行,所以不会出现死锁。
如果找到指定节点,对该节点和其前序节点加锁,因为两个节点都需要改变。因为前序节点总是首先加锁,死锁将不会发生。
下面C程序从单链表中删除一项。
Code Example 3-5 内嵌锁的单链表
Node1_t * delete(int value){
Node1_t * prev, *current;
Prev =&listhead;
Mutex_lock(&prev->lock);
While((current=prev->link)!=NULL){
Mutex_lock(¤t->lock);
If(current->value==value){
Prev->link=current->link;
Mutex_unlock(¤t->lock);
Mutex_unlock(&prev->lock);
Current->link=NULL;
Return(current);
}
mutex_unlock(&prev->lock);
prev=current;
}
mutex_unlock(&prev->lock);
return(NULL);
}
3.1.8内嵌在环状链表中的锁
示例3-6把前例的单链表改为环链表。环链表没有显式的表头;一个线程可以和某个节点连接,对该节点及其邻节点进行操作。等级锁在这里不容易使用,因为其链表是环状的。
Code Example 3-6 Circular Linked List Structure
Typedef struct node 2 {
Int value;
Struct node2 *link;
Mutex_t lock;
} node2_t;
下面的C程序给两个节点加锁,并对它们做操作。
Code Example 3-7 内嵌锁的环链表
Void Hit Neighbor(node2_t *me){
While(1){
Mutex_lock(&me->lock);
If(mutex_lock(&me->link->lock)){
/* failed to get lock*/
mutex_unlock(&me->lock);
continue;
}
break;
}
me->link->value += me->value;
me->value /=2;
mutex_unlock(&me->link->lock);
mutex_unlock(&me->lock);
}
3.2条件变量
用条件变量来自动阻塞一个线程,直到某特殊情况发生。通常条件变量和互斥锁同时使用。
Table3-2 有关条件变量的函数
函数 操作
Cond_init(3T) 初始化条件变量
Cond_wait(3T) 基于条件变量阻塞
Cond_signal(3T) 解除指定线程的阻塞
Cond_timedwait(3T) 阻塞直到指定事件发生
Cond_broadcast(3T) 解除所有线程的阻塞
Cond_destroy(3T) 破坏条件变量
通过条件变量,一个线程可以自动阻塞,直到一个特定条件发生。条件的检测是在互斥锁的保护下进行的。
如果一个条件为假,一个线程自动阻塞,并释放等待状态改变的互斥锁。如 果另一个线程改变了条件,它发信号给关联的条件变量,唤醒一个或多个等待它 的线程,重新获得互斥锁,重新评价条件。
如果两进程共享可读写的内存,条件变量可以被用来实现这两进程间的线程同步。
使用条件变量之前要先进行初始化。而且,在有多个线程等待条件变量时,它们解除阻塞不存在确定的顺序。
3.2.1初始化条件变量
cond_init(3T)
#include
int cond_init(cond_t *cvp, int type, int arg);
用cond_init()初始化有cvp指向的条件变量。Type可以是如下值之一(arg先
不谈):
USYNC_PROCESS 条件变量可以在进程间实现线程同步;
USYNC_THREAD 条件变量只能在进程内部对线程同步;
条件变量可以用分配零内存来初始化,在这种情况下一定要是USYNC_THREAD。
多线程不能同时初始化同一个条件变量。如果一个条件变量正在使用,它不能被重新初始化。
返回值--cond_init()在成功执行后返回零。其他值意味着错误。在以下情况发生时,函数失败并返回相关值。
EINVAL 非法参数
EFAULT mp指向一个非法地址。
3.2.2关于条件变量阻塞
cond_wait(3T)
#include
int cond_wait(cond_t *cvp, mutex_t *mp);
用cond_wait()释放由mp 指向的互斥锁,并且使调用线程关于cvp指向的条件 变量阻塞。被阻塞的线程可以被cond_signal(), cond_broadcast(),或者由fork() 和传递信号引起的中断唤醒。
与条件变量关联的条件值的改变不能从cond_wait()的返回值得出,这样的状 态必须被重新估价。
即使是返回错误信息,Cond_wait()通常在互斥锁被调用线程加锁后返回。
函数阻塞直到条件被信号唤醒。它在阻塞前自动释放互斥锁,在返回前在自动 获得它。
在一个典型的应用当中,一个条件表达式在互斥锁的保护下求值。如果条件表 达式为假,线程基于条件变量阻塞。当一个线程改变条件变量的值时,条件变量获 得一个信号。这使得等待该条件变量的一个或多个线程退出阻塞状态,并试图得到 互斥锁。
因为在被唤醒的线程的cond_wait()函数返回之前条件已经改变,导致等待的 条件在得到互斥锁之前必须重新测试。推荐的办法是在while循环中写条件检查。
Mutex_lock();
While(condition_is_false)
Cond_wait();
Mutes_unlock();
如果有多个线程关于条件变量阻塞,其退出阻塞状态的顺序不确定。
返回值--cond_wait()在成功执行后返回零。其他值意味着错误。在以下

