`
lovnet
  • 浏览: 6701386 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
文章分类
社区版块
存档分类
最新评论

使用pthread库进行多线程编程1 - UNIX环境高级编程第11章读书笔记

阅读更多

11 Threads

1 Introduction

不用介绍了吧

2 Thread Concepts

1. Thread由下面部分组成:

a. Thread ID

b. Stack

c. Policy

d. Signal mask

e. Errno

f. Thread-Specific Data

3 Thread Identification

1. pthread_t用于表示Thread ID,具体内容根据实现的不同而不同,有可能是一个Structure,因此不能将其看作为整数

2. pthread_equal函数用于比较两个pthread_t是否相等

#include <pthread.h>

int pthread_equal(pthread_t tid1, pthread_t tid2)

3. pthread_self函数用于获得本线程的thread id

#include <pthread.h>

pthread _t pthread_self(void);

4 Thread Creation

1. 创建线程可以调用pthread_create函数:

#include <pthread.h>

int pthread_create(

pthread_t *restrict tidp,

const pthread_attr_t *restrict attr,

void *(*start_rtn)(void *), void *restrict arg);

a. pthread_t *restrict tidp:返回最后创建出来的ThreadThread ID

b. const pthread_attr_t *restrict attr:指定线程的Attributes,后面会讲道,现在可以用NULL

c. void *(*start_rtn)(void *):指定线程函数指针,该函数返回一个void *,参数也为void*

d. void *restrict arg:传入给线程函数的参数

e. 返回错误值。

2. pthread函数在出错的时候不会设置errno,而是直接返回错误值

3. Linux系统下面,在老的内核中,由于Thread也被看作是一种特殊,可共享地址空间和资源的Process,因此在同一个Process中创建的不同Thread具有不同的Process ID(调用getpid获得)。而在新的2.6内核之中,Linux采用了NPTL(Native POSIX Thread Library)线程模型(可以参考http://en.wikipedia.org/wiki/Native_POSIX_Thread_Libraryhttp://www-128.ibm.com/developerworks/linux/library/l-threading.html?ca=dgr-lnxw07LinuxThreadsAndNPTL),在该线程模型下同一进程下不同线程调用getpid返回同一个PID。

4. 不能对创建的新线程和当前创建者线程的运行顺序作出任何假设

5 Thread Termination

1. exit, _Exit, _exit用于中止当前进程,而非线程

2. 中止线程可以有三种方式:

a. 在线程函数中return

b. 被同一进程中的另外的线程Cancel

c. 线程调用pthread_exit函数

3. pthread_exitpthread_join函数的用法:

a. 线程A调用pthread_join(B, &rval_ptr),被Block,进入Detached状态(如果已经进入Detached状态,则pthread_join函数返回EINVAL)。如果对B的结束代码不感兴趣,rval_ptr可以传NULL

b. 线程B调用pthread_exit(rval_ptr),退出线程B,结束代码为rval_ptr。注意rval_ptr指向的内存的生命周期,不应该指向BStack中的数据。

c. 线程A恢复运行,pthread_join函数调用结束,线程B的结束代码被保存到rval_ptr参数中去。如果线程BCancel,那么rval_ptr的值就是PTHREAD_CANCELLED

两个函数原型如下:

#include <pthread.h>

void pthread_exit(void *rval_ptr);

int pthread_join(pthread_t thread, void **rval_ptr);

4. 一个Thread可以要求另外一个ThreadCancel,通过调用pthread_cancel函数:

#include <pthread.h>

void pthread_cancel(pthread_t tid)

该函数会使指定线程如同调用了pthread_exit(PTHREAD_CANCELLED)。不过,指定线程可以选择忽略或者进行自己的处理,在后面会讲到。此外,该函数不会导致Block,只是发送Cancel这个请求。

5. 线程可以安排在它退出的时候,某些函数自动被调用,类似atexit()函数。需要调用如下函数:

#include <pthread.h>

void pthread_cleanup_push(void (*rtn)(void *), void *arg);

void pthread_cleanup_pop(int execute);

这两个函数维护一个函数指针的Stack,可以把函数指针和函数参数值push/pop。执行的顺序则是从栈顶到栈底,也就是和push的顺序相反。

在下面情况下pthread_cleanup_push所指定的thread cleanup handlers会被调用:

a. 调用pthread_exit

b. 相应cancel请求

c. 以非0参数调用pthread_cleanup_pop()。(如果以0调用pthread_cleanup_pop(),那么handler不会被调用

有一个比较怪异的要求是,由于这两个函数可能由宏的方式来实现,因此这两个函数的调用必须得是在同一个Scope之中,并且配对,因为在pthread_cleanup_push的实现中可能有一个{,而pthread_cleanup_pop可能有一个}。因此,一般情况下,这两个函数是用于处理意外情况用的,举例如下:

void *thread_func(void *arg)

{

pthread_cleanup_push(cleanup, “handler”)

// do something

Pthread_cleanup_pop(0);

return((void *)0)

}

6. 进程函数和线程函数的相关性:

Process Primitive

Thread Primitive

Description

fork

pthread_create

创建新的控制流

exit

pthread_exit

退出已有的控制流

waitpid

pthread_join

等待控制流并获得结束代码

atexit

pthread_cleanup_push

注册在控制流退出时候被调用的函数

getpid

pthread_self

获得控制流的id

abort

pthread_cancel

请求非正常退出

7. 缺省情况下,一个线程A的结束状态被保存下来直到pthread_join为该线程被调用过,也就是说即使线程A已经结束,只要没有线程B调用pthread_join(A)A的退出状态则一直被保存。而当线程处于Detached状态之时,党线程退出的时候,其资源可以立刻被回收,那么这个退出状态也丢失了。在这个状态下,无法为该线程调用pthread_join函数。我们可以通过调用pthread_detach函数来使指定线程进入Detach状态:

#include <pthread.h>

int pthread_detach(pthread_t tid);

通过修改调用pthread_create函数的attr参数,我们可以指定一个线程在创建之后立刻就进入Detached状态

6 Thread Synchronization

1. 互斥量:Mutex

a. 用于互斥访问

b. 类型:pthread_mutex_t,必须被初始化为PTHREAD_MUTEX_INITIALIZER(用于静态分配的mutex,等价于pthread_mutex_init(…, NULL))或者调用pthread_mutex_initMutex也应该用pthread_mutex_destroy来销毁。这两个函数原型如下:(attr的具体含义下一章讨论)

#include <pthread.h>

int pthread_mutex_init(

pthread_mutex_t *restrict mutex,

const pthread_mutexattr_t *restrict attr)

int pthread_mutex_destroy(pthread_mutex_t *mutex);

c. pthread_mutex_lock用于Lock Mutex,如果Mutex已经被Lock,该函数调用会Block直到MutexUnlock,然后该函数会Lock Mutex并返回。pthread_mutex_trylock类似,只是当MutexLock的时候不会Block,而是返回一个错误值EBUSYpthread_mutex_unlock则是unlock一个mutex。这三个函数原型如下:

#include <pthread.h>

int pthread_mutex_lock(pthread_mutex_t *mutex);

int pthread_mutex_trylock(pthread_mutex_t *mutex);

int pthread_mutex_unlock(pthread_mutex_t *mutex);

2. 读写锁:Reader-Writer Locks

a. 多个线程可以同时获得读锁(Reader-Writer lock in read mode),但是只有一个线程能够获得写锁(Reader-writer lock in write mode)

b. 读写锁有三种状态

i. 一个或者多个线程获得读锁,其他线程无法获得写锁

ii. 一个线程获得写锁,其他线程无法获得读锁

iii. 没有线程获得此读写锁

c. 类型为pthread_rwlock_t

d. 创建和关闭方法如下:

#include <pthread.h>

int pthread_rwlock_init(

pthread_rwlock_t *restrict rwlock,

const pthread_rwlockattr_t *restrict attr)

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

e. 获得读写锁的方法如下:

#include <pthread.h>

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

pthread_rwlock_rdlock:获得读锁

pthread_rwlock_wrlock:获得写锁

pthread_rwlock_unlock:释放锁,不管是读锁还是写锁都是调用此函数

注意具体实现可能对同时获得读锁的线程个数有限制,所以在调用pthread_rwlock_rdlock的时候需要检查错误值,而另外两个pthread_rwlock_wrlockpthread_rwlock_unlock则一般不用检查,如果我们代码写的正确的话。

3. Conditional Variable:条件

a. 条件必须被Mutex保护起来

b. 类型为:pthread_cond_t,必须被初始化为PTHREAD_COND_INITIALIZER(用于静态分配的条件,等价于pthread_cond_init(…, NULL))或者调用pthread_cond_init

#include <pthread.h>

int pthread_cond_init(

pthread_cond_t *restrict cond,

const pthread_condxattr_t *restrict attr)

int pthread_cond_destroy(pthread_cond_t *cond);

c. pthread_cond_wait函数用于等待条件发生(=true)。pthread_cond_timedwait类似,只是当等待超时的时候返回一个错误值ETIMEDOUT。超时的时间用timespec结构指定。此外,两个函数都需要传入一个Mutex用于保护条件

#include <pthread.h>

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);

d. timespec结构定义如下:

struct timespec {

time_t tv_sec; /* seconds */

long tv_nsec; /* nanoseconds */

};

注意timespec的时间是绝对时间而非相对时间,因此需要先调用gettimeofday函数获得当前时间,再转换成timespec结构,加上偏移量。

e. 有两个函数用于通知线程条件被满足(=true):

#include <pthread.h>

int pthread_cond_signal(pthread_cond_t *cond);

int pthread_cond_broadcast(pthread_cond_t *cond);

两者的区别是前者会唤醒单个线程,而后者会唤醒多个线程。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics