Unix高级环境编程系列笔记
通过这篇文字,您将能够解答如下问题:
1. 如何来标识一个线程?
表示进程号的为pid_t类型,表示线程号的是pthread_t类型。pthread_t是一个结构体而不是整型。
使用pthread_equal确定两个线程号是否相等:
#includeint pthread_equal(pthread_t tid1, pthread_t tid2);Returns: nonzero if equal, 0 otherwise
使用pthread_self函数来获取线程的ID:
#includepthread_t pthread_self(void);Returns: the thread ID of the calling thread
使用pthread_create函数创建一个新线程。
以下是代码片段: #include int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void), void *restrict arg); Returns: 0 if OK, error number on failure |
当该函数成功返回的时候,tidp所指向的内存位置将被分配给新创建的带有thread ID的线程。
attr用来定制各种线程参数。
新创建的线程将在start_rtn函数所指向的地址开始运行,该函数接受一个参数无类型的指针arg作为参数
线程创建时无法保证哪个线程会先运行。新创建的线程可以访问进程地址空间,并且继承了调用线程的浮点环境以及信号量掩码,但对于线程的未决信号量也将会被清除。
下面的这段程序创建新的线程,并打印线程id,新线程通过pthread_self函数获取自己的线程ID。
#include "apue.h" #includepthread_t ntid;void printids(const char *s){ pid_t pid; pthread_t tid; pid = getpid(); tid = pthread_self(); printf("%s pid %u tid %u (0x%x)\n", s, (unsigned int)pid, (unsigned int)tid, (unsigned int)tid);}void * thr_fn(void *arg){ printids("new thread: "); return((void *)0);}int main(void){ int err; err = pthread_create(&ntid, NULL, thr_fn, NULL); if (err != 0) err_quit("can't create thread: %s\n", strerror(err)); printids("main thread:"); sleep(1); exit(0);}
如果一个线程调用了exit, _Exit, 或者_exit,将导致整个进程的终止。要实现单个线程的退出,可以采用如下方式:
o 线程可以简单的从start routine返回,返回值就是线程的退出代码。
o 线程可以被同一进程中的其它线程终止。
o 线程调用pthread_exit
#includevoid pthread_exit(void *rval_ptr);
4.如何使调用线程阻塞等待指定线程的退出,并获得退出线程的返回码?
#includeint pthread_join(pthread_t thread, void **rval_ptr);Returns: 0 if OK, error number on failure
调用线程将会被阻塞直到指定的线程终止。如果线程简单的从start routine返回则rval_ptr将包含返回代码。如果线程是被撤销(调用pthread_exit)的,rval_ptr指向的内存地址将被设置为PTHREAD_CANCELED.
通过调用pthread_join,我们自动的将一个线程变成分离状态,这样就可以实现资源的回收。如果线程已经处于分离状态,调用pthread_join将会失败,并返回EINVAL。
如果我们对于线程的返回值不感兴趣,可以将rval_ptr设置成NULL。
一段有缺陷的代码:
#include "apue.h" #includestruct foo { int a, b, c, d;};voidprintfoo(const char *s, const struct foo *fp){ printf(s); printf(" structure at 0x%x\n", (unsigned)fp); printf(" foo.a = %d\n", fp->a); printf(" foo.b = %d\n", fp->b); printf(" foo.c = %d\n", fp->c); printf(" foo.d = %d\n", fp->d);}void *thr_fn1(void *arg){ struct foo foo = {1, 2, 3, 4}; printfoo("thread 1:\n", &foo); pthread_exit((void *)&foo);}void *thr_fn2(void *arg){ printf("thread 2: ID is %d\n", pthread_self()); pthread_exit((void *)0);}intmain(void){ int err; pthread_t tid1, tid2; struct foo *fp; err = pthread_create(&tid1, NULL, thr_fn1, NULL); if (err != 0) err_quit("can't create thread 1: %s\n", strerror(err)); err = pthread_join(tid1, (void *)&fp); if (err != 0) err_quit("can't join with thread 1: %s\n", strerror(err)); sleep(1); printf("parent starting second thread\n"); err = pthread_create(&tid2, NULL, thr_fn2, NULL); if (err != 0) err_quit("can't create thread 2: %s\n", strerror(err)); sleep(1); printfoo("parent:\n", fp); exit(0);}
注意,pthread_create 和 pthread_exit函数的无类型指针可以传递复杂的结构信息,但这个结构所使用的内存在调用者完成后必须仍然有效(分配在堆上或者是静态变量),否则就会出现使用无效的错误。这段代码中thr_fn1函数中变量foo分配在栈上,但该线程退出后,主线程通过pthread_join获取foo的地址并进行操作(调用printfoo函数时)就会出现错误,因为此时thr_fn1已经退出它的栈已经被销毁。
调用pthread_cancel函数将导致tid所指向的线程终止运行。但是,一个线程可以选择忽略其它线程控制该线程何时退出。注意,该函数并不等待线程终止,它仅仅提出要求。
#includeint pthread_cancel(pthread_t tid);Returns: 0 if OK, error number on failure
线程可以建立多个清理处理程序,这些程序记录在栈中,也就是说他们的执行顺序与注册顺序想法。使用如下函数注册清理函数:
void pthread_cleanup_push(void (*rtn)(void *), void *arg);
void pthread_cleanup_pop(int execute);
rtn将被调用,并传以arg参数,引起该函数调用的情况如下:
o 调用pthread_exit
o 对于退出请求的反应
o 以非0参数调用pthread_cleanup_push
如果pthread_cleanup_pop的参数非0则仅仅移除该处理函数而不执行。
如果函数已经处于分离状态,则当它退出时线程底层的存储资源会被立即回收。处于分离状态的线程,如果调用pthread_join来等待其退出将会出现错误。
通过下列函数可以让进程处于分离状态:
#includeint pthread_detach(pthread_t tid);Returns: 0 if OK, error number on failure
使用pthreads mutual-exclusion interfaces。引入了mutex,用pthread_mutex_t类型来表示。在使用这个变量之前,我们首先要将其初始化,或者赋值为PTHREAD_MUTEX_INITIALIZER(仅仅用于静态分配的mutexs),或者调用pthread_mutex_init。如果我们动态的为mutex分配空间(例如通过调用malloc),我们需要在调用free释放内存之前调用pthread_mutex_destroy。
函数定义如下:
以下是代码片段: #include int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); int pthread_mutex_destroy(pthread_mutex_t *mutex); Both return: 0 if OK, error number on failure |
初始化mutex时参数attr用来指定mutex的属性,要使用默认值将它设置为NULL。
使用如下函数对mutex进行加锁或解锁:
int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_trylock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex); All return: 0 if OK, error number on failure
注意当mutex已经被加锁则 pthread_mutex_lock会阻塞。如果一个线程无法忍受阻塞,可以调用pthread_mutex_trylock来加锁,加锁失败则立即返回EBUSY。
如果一个线程对mutex加两次锁则显然会导致死锁。但实际上死锁的情况要复杂的多:when we use more than one mutex in our programs, a deadlock can occur if we allow one thread to hold a mutex and block while trying to lock a second mutex at the same time that another thread holding the second mutex tries to lock the first mutex. Neither thread can proceed, because each needs a resource that is held by the other, so we have a deadlock.
死锁可以通过控制加锁的顺序来避免。有两个mutex A和B,如果所有的线程总是先对A加锁再对B加锁就不会产生死锁。但实际应用中可能很难保证这种顺序加锁的方式,这种情况下,可以使用pthread_mutex_trylock来避免死锁的发生。
读写锁的初始化与销毁:
以下是代码片段: #include int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr); int pthread_rwlock_destroy(pthread_rwlock_t *rwlock); Both return: 0 if OK, error number on failure |
加锁与解锁:
#includeint pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);All return: 0 if OK, error number on failure
对于读者的数量会有限制,因此调用 pthread_rwlock_rdlock时需要检查返回值。
在正确使用的情况下,不需要检查pthread_rwlock_wrlock和pthread_rwlock_unlock的返回值。
条件加锁:
#includeint pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);Both return: 0 if OK, error number on failure
条件变量是线程可用的另外一种同步机制。条件变量给多个线程提供了一个会合的场所。条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定条件的发生。条件本身是由互斥量保护的。线程在改变状态前必须首先锁住互斥量,其它线程在获得互斥量之前不会觉察到这种变化。
条件变量的类型为pthread_cond_t ,其初始化与销毁的方式与mutex类似,注意静态变量可以通过指定常量PTHREAD_COND_INITIALIZER来进行初始化。
#includeint pthread_cond_init(pthread_cond_t *restrict cond, pthread_condattr_t *restrict attr);int pthread_cond_destroy(pthread_cond_t *cond);Both return: 0 if OK, error number on failure
使用pthread_cond_wait来等待条件变成真。
函数定义如下:
以下是代码片段: #include int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex); int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict timeout); Both return: 0 if OK, error number on failure |
注意,调用成功返回后,线程需要重新计算条件变量,因为其它线程可能已经改变了条件。
有两个函数用于通知线程一个条件已经被满足。pthread_cond_signal函数用来唤醒一个等待条件满足的线程, pthread_cond_broadcast用来唤醒所有等待条件满足的线程。
他们的定义为:
#includeint pthread_cond_signal(pthread_cond_t *cond);int pthread_cond_broadcast(pthread_cond_t *cond);Both return: 0 if OK, error number on failure
下面的这段代码实现了类似于生产者消费者模型的程序,生产者通过enqueue_msg将消息放入队列,并发送信号通知给消费者线程。消费者线程被唤醒然后处理消息。
#includestruct msg { struct msg *m_next; /* ... more stuff here ... */};struct msg *workq;pthread_cond_t qready = PTHREAD_COND_INITIALIZER;pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER;voidprocess_msg(void){ struct msg *mp; for (;;) { pthread_mutex_lock(&qlock); while (workq == NULL) pthread_cond_wait(&qready, &qlock); /*get msg from the queue*/ mp = workq; workq = mp->m_next; pthread_mutex_unlock(&qlock); /* now process the message mp */ }}voidenqueue_msg(struct msg *mp){ pthread_mutex_lock(&qlock); /*put msg in queue*/ mp->m_next = workq; workq = mp; pthread_mutex_unlock(&qlock); pthread_cond_signal(&qready);}
在pthread_cond_signal发送消息之前并不需要占用锁,因为一旦线程被唤醒后通过while发现没有要处理的msg存在则会再次陷入睡眠。如果系统不能容忍这种竞争环境,则需要在unlock之前调用cond_signal,但是在多处理器机器上,这样会导致多线程被唤醒然后立即进入阻塞(cond_signal唤醒线程,但由于我们仍占用着锁,所以这些线程又会立即阻塞)。
建议继续学习:
- 高性能web服务器-读书笔记 (阅读:6843)
- MySQL 应用小笔记 (阅读:3454)
- JavaScript中级笔记 (阅读:3425)
- Unix高级环境编程系列笔记 (阅读:3417)
- 《精通CSS+DIV》学习笔记 (阅读:3097)
- 读书笔记-交互设计精髓[1] (阅读:2817)
- 《高性能网站建设指南》笔记 (阅读:2577)
- 读《Web 表单设计》 (阅读:2264)
- 《瞬间之美》读书笔记 (阅读:2056)
- 读书:《SEO实战密码》 (阅读:1867)
扫一扫订阅我的微信号:IT技术博客大学习
- 作者:nebula 来源: Nebula's Cyberspace
- 标签: Unix高级环境编程 笔记
- 发布时间:2011-02-27 23:00:05
- [57] IOS安全–浅谈关于IOS加固的几种方法
- [56] Oracle MTS模式下 进程地址与会话信
- [55] 图书馆的世界纪录
- [54] android 开发入门
- [53] 如何拿下简短的域名
- [52] Go Reflect 性能
- [51] 【社会化设计】自我(self)部分――欢迎区
- [48] 读书笔记-壹百度:百度十年千倍的29条法则
- [39] 程序员技术练级攻略
- [34] 视觉调整-设计师 vs. 逻辑