Unix多线程编程技术

http://blog.csdn.net/zkf11387/article/details/7657055

posix pthreads库提供了一系列的编写多线程程序的函数主要包括

1. 创建和中止线程函数
2. 同步线程和对程序资源加锁函数
3. 管理线程时序函数
一般地使用线程时序管理函数会复杂你的程序算法不仅如此在你移植你在单处
理机上的多线程程序到多处理机环境时也可能会带来麻烦所以这里不讨论它
每一个线程都可以访问到相同的全局变量和文件但每个线程也有它自己的堆栈和寄
存器
pthread_create函数
当一个程序被exec开始执行时会创建一个线程该线程称作初始线程(initial
thread)或主线程(main thread) 其他的线程使用pthread_create创建
#include <pthread.h>
int pthread_create( pthread_t *tid, const pthread_attr_t *attr,
void *(*func)(void*), void* arg);
Returns: 0 if OK, 正数Exxx value on error.
一个进程中的每个线程都有一个线程ID来标识线程ID的数据类型为pthread_t
(通常为 unsigned int ) 如果创建新线程成功它的ID通过tid指针返回
每个线程有很多属性调度优先级初始堆栈大小以daemon方式运行等等
我们可以通过pthread_attr_t变量来设定这些属性如果attr为一个空指针线程的属性
为缺省值通常情况下我们都使用缺省值
当创建一个线程的时候通常都会指定一个开始函数(start function) 线程在开
始执行时调用该函数然后线程显式地(调用pthread_exit)或隐含地(执行函数start
function返回)终止开始函数地址为func变量该函数调用时有一个指针参数arg 如果
开始函数需要多个参数必须构造一个结构把所有的参数作为结构的成员然后把
结构指针的地址作为唯一的参数传递给开始函数(start function)
注意变量func和arg 函数func有一个通用类型指针的变量(void *) 返回一个通
用类型的指针(void *) 这允许我们传递一个指针(可以指向任意类型)给线程线程返
回一个指针(指向任务类型)
通常情况下如果执行成功Pthread函数返回0 失败时返回非0 和socket函数
以及大多数的系统调用不同这些函数出错时返回1 和一个正的errno值指明失败原
因Pthread返回一个正的错误号作为返回值例如如果在调用pthread_create创建新
线程时因为超过了系统线程数的限制而失败则pthread_create返回EAGAIN
Pthread函数不使用errno变量成功返回0 失败返回一个正数的约定是考虑周到的因
为所有定义在<sys/errno.h>的出错参数Exxx的值都是正数并且0是没有使用的
pthread_join函数
调用pthread_join可以等待指定的线程终止把线程与进程相对比的话
pthread_create相当于fork pthread_join相当于waitpid
#include <pthread.h>
1 2002-08-20
int pthread_join( pthread_t tid, void **status );
Returns:0 if OK positive Exxx 值 on error
我们必须指定等待线程的tid 非常不幸我们没有方法等待任意一个线程的终
止(类似于waitpid中的进程ID参数的值为1) 我们将在图23.13中继续讨论这个问题
如果status的指针为非NONE 线程的返回值(是一个指向某对象的指针)保存在
被status所指向的本地地址
当一个线程调用了pthread_join后此线程进入睡眠状态直到pthread_join指定的线
程中止后才继续执行pthread_join函数主要的功能是来回收指定线程的系统资源如
果你在程序中忘记了调用pthread_join 而有用pthread_create创建了多过线程程序运
行时可能会因为资源不足而出问题这就想你多次调用了malloc函数而忘记了调用
free函数一样
有时候你可能不关心一个线程的存在状态 而只是要求那个线程为你完成一项工
作这时候你可以用pthread_detach函数被pthread_detach指定后的线程在退出时会自
动释放占据的系统资源
pthread_self 函数
在一个进程中所有的线程都有一个ID来标识线程ID在调用pthread_create函数
时返回在pthread_join函数中也使用了线程ID 一个线程可以使用pthread_self来取得
其本身的线程ID
#include <pthread.h>
pthread_t pthread_self( void );
Returns: thread ID of calling thread.
pthread_self类似于UNIX进程的getpid
pthread_detach 函数
线程分为可连接的(joinable)和不可连接的(detached) 缺省状态下线程是可连接
的当一个可连接的线程终止时它的线程ID和终止状态将一直保存到有另外一个线
程调用pthread_join 一个不可连接的线程类似于幽灵(daemon)进程当它终止时将
释放所有的资源并且无法等待它的终止如果一个线程需要知道另外一个线程何时
终止最好让其为可连接的(joinable)
pthread_detach函数将指定的进程变为不可连接的(detached)
#include <pthread.h>
int pthread_detach( pthread_t tid );
Returns: 0 if OK 正的Exxx值 on error
此函数最常用的情况是使某线程本身不可连接如
pthread_detach( pthread_self());
pthread_exit 函数
线程终止的一种方法是调用pthread_exit函数
2 2002-08-20
#include <pthread.h>
void pthread_exit( void *status);
如果线程不是detached 它的线程ID和终止状态将一直保存到进程中有其他的线
程调用函数pthread_join
status指针一定不能指向线程的局部变量因为在线程终止时该变量也会随之消

另外还有两种情况会导致线程终止
(1)线程开始执行时调用的函数(pthread_create的第三个变量)返回既然开始函数
必须返回一个void指针因此函数的返回值就是线程的终止状态
(2)进程的main函数返回或任何一个线程调用exit时进程及其所有的线程终止
pthread_mutex_lock函数
当线程要访问(读或写)共享数据的时候必须对共享数据加锁例
int i; //i是全局数据
pthread_mutex_t iLock;
...
pthread_mutex_lock(&iLock);
//to do something to i
pthread_mutex_unlock(&iLock);
...
当线程调用pthread_mutex_lock把共享数据锁住后其他线程要使用此数据时就会被暂
时挂起直到线程调用pthread_mutex_unlock解锁
需要注意的时一个线程不应该一直锁住共享数据即调用pthread_mutex_lock后应该尽
可能快调用pthread_mutex_unlock释放共享区以便其他线程可以利用一般做法时
当要读取一个共享数据时先调用pthread_mutex_lock加锁数据把共享数据拷贝到一
个临时本地变量中去立即调用pthread_mutex_unlock解锁
当 要 写 一 个 共 享 数 据 时 先把要写的数据准备在本地临时变量中然后调用
phtread_mutex_lock加锁共享数据把计算好了的数据写入共享数据区然后立即调用
pthread_mutex_unlock解锁
pthread_mutex_unlock函数
解锁函数
pthread_mutex_init函数
初始化一个锁变量
pthread_mutex_destroy函数
释放一个锁变量
pthread_mutex_t mutex;
pthread_mutex_destory(&mutex);
pthread_cond_init函数
初始化条件变量
pthread_cont_t cond;
pthread_cond_init(&cond);
pthread_cond_wait函数
3 2002-08-20
调用此函数会导致一个线程等待指定的条件变量被signaled或broadcasted 看下
面的语句
pthread_mutex_lock(&mutex); /* lock mutex */
while (!predicate) { /* check predicate */
pthread_cond_wait(&condvar,&mutex); /* go to sleep - recheck
pred on awakening */
}
pthread_mutex_unlock(&mutex); /* unlock mutex */
当predicate等于0时程序将调用pthread_cond_wait pthread_cond_wait将做下面的两件

Ÿ unlocks the mutex
Ÿ puts the thread to sleep
线程这时将进入睡眠状态为了唤醒它在另外一个线程中可以用
pthread_mutex_lock(&mutex); /* lock the mutex */
predicate=1; /* set the predicate */
pthread_cond_broadcast(&condvar); /* wake everyone up */
pthread_mutex_unlock(&mutex); /* unlock the mutex */
pthread_cond_broadcast函数将唤醒所以的在等待条件变量condvar的线程
使用概要
1. 条件变量一个线程调用pthread_cond_wait后将进入睡眠状态直到另外一个线程调
用pthread_cond_broadcast来唤醒条件变量用来完成这个行为假如我们只想唤醒
单个进程可以用pthread_cond_signal函数
2. predicate 变量这个变量来决定是否要调用pthead_cond_wait函数也就是说决
定线程是否要进入睡眠状态使用这个变量的原因是因为唤醒线程只知道要唤醒
那些进入睡眠状态的线材而它并不知道有多少线程进入了睡眠状态假如没有
predicate变量一个后来的线程可能错过另一个线程已经发出的broadcast而进入睡
眠状态这种情况可能不是你想要的
3. mutex 用互斥保证了线程安全存取predicate变量由于睡眠线程要读取predicate
唤醒线程要写变量predicate 所以必须对predicate使用互斥机制
为什么要使用一个while循环呢这是因为一次只会唤醒一个睡眠的线程假如第
一个被唤醒的线程退出了循环并且又把predicate改为了0 则其他的进程就不会被唤
醒这就是while带来的好处
为什么pthread_cond_wait需要知道mutex变量呢我们来看一下下面把mutex分离出
来会产生什么后果
pthread_mutex_lock(&mutex); /* lock mutex */
while (!predicate) { /* check predicate */
pthread_mutex_unlock(&mutex); /* unlock mutex */
pthread_cond_wait(&condvar); /* go to sleep - recheck
pred on awakening */
pthread_mutex_lock(&mutex); /* lock mutex */
}
pthread_mutex_unlock(&mutex); /* unlock mutex */
这段程序会产生很大的问题假如线程进入了while循环执行了
pthread_mutex_unlock函数这时被切换到另外一个线程而那个线程又请求了
mutex和设置了predicate变量这就导致了broadcast先于上面现成的pthread_cond_wait 执
行也就是说这个本来应该进入睡眠的线程却没有进入所以mutex和条件变量不应
4 2002-08-20
该分开操作pthread_cond_wait处理mutex和条件变量unix的内核会保证线程不会丢
失broadcast
pthread_cond_broadcast函数
这个唤醒那些等待条件变量的线程
sched_yield函数
通知线程调度程序调用另外一个线程运行
当一个线程调用sched_yield后将强迫自己放弃处理机直到它又处于线程列表
的头部位置时才运行线程调用sched_yield函数通知线程调度程序调度一个级别和自
己相等或级别大于自己的线程如果没有和自己级别相等或大于的线程则线程继续
执行
线程安全
我们知道所有的全局数据在线程中被存取的时候是要用互斥机制来保护的而
当我们在线程中调用了系统的库函数时情况又会是什么样呢由于你调用的系统函
数可能会使用到全局数据而它有没有提供保护机制问题就出来了
一个例子是malloc函数malloc函数从全局的共享数据区分配一块数据空间因
此加入同时有多个线程调用malloc 混乱的就有可能产生哦当然你可能会考虑用
lock/unlock机制来保证malloc不出这个问题但这是没有用的为什么
假设每个线程调用了printf函数
printf("i=%d s=%s\n",anint,astring);
printf函数是有能暗中调用的malloc函数来分配缓冲区来打印astring字符串的而printf可
能根本不知道你的malloc调用要用到互斥机制保护所以假如有多个的线程来调用
printf和malloc 错误就可能产生
上面的例子说明了库函数可能产生的问题但不幸的是没有一般的方法来解
决这个问题POSIX threads规定了C语言函数库是要thread-safe的而你如果使用其他
的没有说明thread-safe的库就必须手工来实现线程安全
5 2002-08-20

版权声明:本文为博主原创文章,未经博主允许不得转载。

猜你喜欢

转载自blog.csdn.net/woshidenghaitao/article/details/48380745