UNIX下编写多线程程序


UNIX多线程编程技术 posix pthreads库提供了一系列的编写多线程程序的函数 主要包括 1. 创建和中止线程函数 2. 同步线程和对程序资源加锁函数 3. 管理线程时序函数 一般地 使用线程时序管理函数会复杂你的程序算法 不仅如此 在你移植你在单处 理机上的多线程程序到多处理机环境时也可能会带来麻烦 所以这里不讨论它 每一个线程都可以访问到相同的全局变量和文件 但每个线程也有它自己的堆栈和寄 存器 pthread_create函数 当一个程序被exec开始执行时会创建一个线程 该线程称作初始线程(initial thread)或主线程(main thread) 其他的线程使用pthread_create创建 #include 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 失败返回一个正数的约定是考虑周到的 因 为所有定义在的出错参数Exxx的值都是正数 并且0是没有使用的 pthread_join函数 调用pthread_join可以等待指定的线程终止 把线程与进程相对比的话 pthread_create相当于fork pthread_join相当于waitpid #include 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_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 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 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
还剩4页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 15 金币 [ 分享pdf获得金币 ] 0 人已下载

下载pdf

pdf贡献者

bosco

贡献于2011-05-26

下载需要 15 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf