转自:
http://blog.csdn.net/atfield/article/details/1526225
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:返回最后创建出来的Thread的Thread 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_Library和http://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_exit和pthread_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指向的内存的生命周期,不应该指向B的Stack中的数据。
c. 线程A恢复运行,pthread_join函数调用结束,线程B的结束代码被保存到rval_ptr参数中去。如果线程B被Cancel,那么rval_ptr的值就是PTHREAD_CANCELLED。
两个函数原型如下:
#include <pthread.h> void pthread_exit(void *rval_ptr); int pthread_join(pthread_t thread, void **rval_ptr);
4. 一个Thread可以要求另外一个Thread被Cancel,通过调用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_init。Mutex也应该用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直到Mutex被Unlock,然后该函数会Lock Mutex并返回。pthread_mutex_trylock类似,只是当Mutex被Lock的时候不会Block,而是返回一个错误值EBUSY。pthread_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_wrlock和pthread_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结构定义如下:
注意timespec的时间是绝对时间而非相对时间,因此需要先调用gettimeofday函数获得当前时间,再转换成timespec结构,加上偏移量。struct timespec { time_t tv_sec; /* seconds */ long tv_nsec; /* nanoseconds */ };
e. 有两个函数用于通知线程条件被满足(=true):
#include <pthread.h> int pthread_cond_signal(pthread_cond_t *cond); int pthread_cond_broadcast(pthread_cond_t *cond);
两者的区别是前者会唤醒单个线程,而后者会唤醒多个线程。
1 Thread Limits
用sysconf函数可以获得和thread相关的一些系统信息,主要是线程相关的一些最大值:
NAME | Description | Argument |
PTHREAD_DESTRUCTOR_ITERATIONS | 最大尝试销毁线程相关数据(Thread Specific Data)的次数,见下面关于Thread-Specific Data的内容 | _SC_THREAD_DESTRUCTOR_ITERATIONS |
PTHREAD_KEYS_MAX | 一个进程所能够创建的最大键数 | _SC_THREAD_KEYS_MAX |
PTHREAD_STACK_MIN | 线程栈的最小值 | _SC_THREAD_STACK_MIN |
PTHREAD_THREADS_MAX | 单个进程中的线程个数最大值 | _SC_THREAD_THREADS_MAX |
部分概念在后面会提到。
虽然标准定义了这些常量,不过在很多系统上面可能根本就没有定义对应的Argument(如_SC_THREAD_DESTRUCTOR_ITERATIONS可能未定义),或者sysconf函数返回错误。因此在很多时候这些很难派上用场。
2 Thread Attributes
在前面讲到pthread_create等函数的时候,这些函数有一个参数pthread_attr_t。缺省情况下可以传NULL。但是如果想自己定义线程的相关属性的话,应该调用pthread_attr_init函数来定义:
#include <pthread.h>
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);
返回0表示正常,出错时返回错误值
pthread_attr_init函数负责初始化pthread_attr_t结构为缺省值。pthread_attr_destroy负责释放在pthread_attr_init函数调用时分配的内存,同时将pthread_attr的内容置为非法。如果要修改属性,需要调用其他函数来手动设置。
基本的线程属性如下:
Name | Description |
detachstate | detached状态,在前一章中有讲述 |
guardsize | 线程栈底部的Guard缓冲区的大小 |
stackadddr | 线程栈的最低地址 |
stacksize | 线程栈的大小 |
1. Detached State:一个线程如果出于Detached状态,说明此线程在退出的时候可以立刻释放其资源和对应的结束代码,从而无法使用pthread_join。可以用pthread_attr_setdetachedstate函数来设置Detach状态。传入PTHREAD_CREATE_DETACHED可以让线程启动的时候就处于Detached状态,而传入PTHREAD_CREATE_JOINABLE则是以通常状态启动线程
#include <pthread.h>
int pthread_attr_getdetachedstate(const pthread_attr_t *restrict attr, int *detachstate);
int pthread_attr_setdetachedstate(pthread_attr_t *restrict attr, int detachstate);
返回0表示正常,出错时返回错误值
2. GuardSize:在线程栈的末尾有一个比较小的内存区域,这个内存区域是保护起来的,一旦栈发生overflow,系统立刻就会知道,发送一个Signal(Windows也有类似的功能,只不过是用于自动增长栈的大小)。缺省情况下这个大小正好是一个页=PAGESIZE。甚至可以用函数将该数值设置为0来禁止这个功能。如果我们修改了栈地址的话,系统会认为我们会自己处理Overflow的问题,因此也不会提供这个功能。调用pthread_attr_get_guardsize & pthread_attr_set_guardsize可以获得/设置这个值:
#include <pthread.h>
int pthread_attr_getguardsize(const pthread_attr_t *restrict attr, size_t *restrict guardsize);
int pthread_attr_setguardsize(pthread_attr_t *restrict attr, size_t guardsize);
返回0表示正常,出错时返回错误值
3. StackSize:线程可以自己设置栈的大小,用pthread_attr_getstacksize和pthread_attr_setstacksize:
#include <pthread.h>
int pthread_attr_getguardsize(const pthread_attr_t *restrict attr, size_t *restrict stacksize);
int pthread_attr_setguardsize(pthread_attr_t *restrict attr, size_t stacksize);
返回0表示正常,出错时返回错误值
4. StackAddr:当进程中线程过多的时候,有可能会栈空间不足。一个方案是用malloc或者nmap来分配新的内存,作为一个另外的栈,供线程使用。可以调用pthread_attr_setstack和pthread_attr_getstack来获得/设置:
#include <pthread.h>
int pthread_attr_getstack(const pthread_attr_t *restrict attr, void **restrict stackaddr, size_t *restrict stacksize);
int pthread_attr_setstack(pthread_attr_t *restrict attr, void *stackaddr, size_t *stacksize)
返回0表示正常,出错时返回错误值
除此之外,还有其他一些线程属性:
1. Cancellability State
2. Cancellability Type
3. Concurrency Level
1和2会在第6节中讲述。
Concurrency Level定义了用户模式线程和内核线程/进程之间的对应关系。如果具体操作系统实现是按照1对1,也就是一个用户模式线程对应一个内核模式线程的话,那么修改这个值没有作用。但是如果操作系统实现用少量内核模式线程/进程来模拟用户模式线程的话,那么修改这个值可能会提高或者降低程序和系统的性能。Level值并没有具体的意义,只是一个hint。Level=0表示让系统自动选择。函数原型如下:
#include <pthread.h>
int pthread_attr_getconcurrency(void);
int pthread_attr_setconcurrency(int level);
返回0表示正常,出错时返回错误值
注意这个属性不是和具体线程相关的,而是系统级别的。
3 Synchronization Attributes
同步对象也有他们自己的Attributes。
3.1 Mutex Attributes
Mutex的属性类型为pthread_mutexattr_t。可以用pthread_mutexattr_init和pthread_mutexattr_destroy来创建和释放Mutex Attributes。类似的,pthread_mutexattr_init函数会将结构初始化为缺省值。
#include <pthread.h>
int pthread_mutexattr_init(pthread_mutexattr_t *attr);
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
返回0表示正常,出错时返回错误值
Mutex的属性有:
1. Process-Shared:指定Mutex是否为多个进程所共享。缺省值是PTHREAD_PROCESS_PRIVATE,即只有创建者进程才可以访问此Mutex。也可以设置为PTHREAD_PROCESS_SHARED,在多个进程之间共享。
#include <pthread.h>
int pthread_mutexattr_getpshared(const pthread_mutexattr_t *restrict attr, int *restrict pshared);
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared);
返回0表示正常,出错时返回错误值
2. Type:指定Mutex的类型。Mutex有下列类型:
Type | 未释放锁的情况获得锁 | 未获得锁情况下释放锁 | 已释放锁的情况下再次释放 | Description |
PTHREAD_MUTEX_NORMAL | 死锁 | 未定义 | 未定义 | 一般的Mutex |
PTHREAD_MUTEX_ERRORCHECK | 出错 | 出错 | 出错 | 加强错误检查 |
PTHREAD_MUTEX_RECURSIVE | 允许 | 出错 | 出错 | 允许单个线程获得锁多次,需要多次释放,但是不能超过获得锁的次数。一般用来处理可重入的函数,见下面一章 |
PTHREAD_MUTEX_DEFAULT | 未定义 | 未定义 | 未定义 | 完全没有错误检查 |
通过调用pthread_mutexattr_gettype & pthread_mutexattr_settype来获得/设置对应的type:
#include <pthread.h>
int pthread_mutexattr_gettype(const pthread_mutexattr_t *restrict attr, int *restrict type);
int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type);
返回0表示正常,出错时返回错误值
3.2 Reader-Writer Lock Attributes
类似Mutex Attributes,但是只支持Process Shared属性。
3.3 Condition Variable Attributes
类似Mutex Attributes,但是只支持Process Shared属性。
4 Reentrancy
1. 大部分Single UNIX Specification所定义的函数都是线程安全的,但是也有不少例外。实际使用的时候建议参考文档,确定函数是否是线程安全。
2. 文件支持用ftrylockfile, flockfile, funlockfile来锁定文件访问。标准IO函数被要求必须调用在内部实现中调用flockfile, funlockfile。基于字符的部分IO函数具有非线程安全版本,以_unlocked结尾,如:getchar_unlocked, getc_unlocked, putchar_unlocked, putc_unlocked
3. 书中提供了一个可重入的getenv_r实现。要点是:
a. 用到了Recursive Mutex(使用pthread_mutexattr_settype函数调用设置)来保护自己和其他线程冲突(普通的Mutex就可以做到),同时允许重入(必须用Recursive Mutex)
b. 要求调用者提供自己的buffer,而不是用静态全局变量envbuf来访问结果
c. 使用pthread_once函数保证只调用一个初始化函数一遍,用于初始化Mutex(当然用其他方法也可以)
5 Thread-Specific Data
1. Thread-Specific Data是一种很方便的将数据和线程联系起来的方法,在C Runtime中也大量用到Thread-Specific Data来维护线程相关的数据,一个典型的例子是errno:实际上errno是一个函数调用,返回和线程相关的错误值。Windows中有类似的机制,称为TLS (Thread Local Storage)
2. 访问Thread-Specific Data需要使用Key。不同线程使用同一个key访问同一类型的数据(比如Errno),但是可以存放不同的值。Key的类型为pthread_key_t
3. 用pthread_key_create函数创建key:
#include <pthread.h>
int pthread_key_create(pthread_key_t *keyp, void (*destructor)(void *))
返回0表示正常,出错时返回错误值
创建好之后key对应的Thread-Specific Data为NULL。
Destructor函数指针指定当pthread_key_t被删除的时候需要自动调用的函数,可以传NULL。参数值为TSD的具体值,必然非NULL。在线程正常退出时候,如return或者pthread_exit,当数据值为非NULL时候会调用。但是当线程非正常退出,如调用exit, _exit, _Exit, abort或者其他非正常退出的时候,destructor不会被调用。一般情况下,这个destructor用来销毁用户用malloc为Thread-Specific Data分配的空间。注意:一般不应该用destructor来调用pthread_key_delete,因为delete对于一个key只用调一次,而destructor是对每个线程都调用的,前提是线程正常退出并且TSD不为NULL。
Key的总数量可能会有限制。可以用PTHREAD_KEYS_MAX来查询最大值。
因为调用Destructor的时候这个Destructor可能又会创建新的Key,所以当线程退出的时候,调用Destructor的过程会反复继续直到没有key具有非NULL值或者次数到达最大值PTHREAD_DESTRUCTOR_ITERATIONS为止。这个值可以用sysconf获得。
4. 用pthread_key_delete函数删除key:
#include <pthread.h>
int pthread_key_delete(pthread_key_t *keyp)
返回0表示正常,出错时返回错误值
注意,调用此函数不会导致Destructor被调用!
5. 可以用pthread_once函数保证某个函数只被调一次,用法如下:
#include <pthread.h>
pthread_once_t initflag = PTHREAD_ONCE_INIT;
int pthread_once(pthread_once_t *initflag, void (*initfn)(void));
返回0表示正常,出错时返回错误值
即使在多个线程中被调,pthread_once保证initfn在进程中只会调用一次。
6. pthread_getspecific和pthread_setspecific用于访问key所对应的Thread-Specific Data,就是一个void *指针:
#include <pthread.h>
Void * pthread_getspecific(pthread_key_t key)
返回和key相关的当前线程的Thread-Specific Data
int pthread_setspecific(pthread_key_t key, const void *value)
返回0表示正常,出错时返回错误值
6 Cancel Options
除了上面介绍的Thread Attributes之外,还有两个Thread Attributes没有介绍,这两个均和pthread_cancel函数有关。
1. Cancelability State:表示允许或者禁止pthread_cancel调用。PTHREAD_CANCEL_ENABLE和PTHREAD_CANCEL_DISABLE分别对应允许和禁止(缺省情况下自然是允许)。调用pthread_setcancelstate来设置状态:
#include <pthread.h>
int pthread_setcancelstate(int state, int *oldstate)
返回0表示正常,出错时返回错误值
注意:这个函数对当前线程有效,并非设置pthread_attr_t
如果对一个线程调用pthread_cancel,线程会继续运行知道线程到达一个Cancellation Point,也就是可撤销点。POSIX.1定义了一些可以作为Cancellation Point的一些函数。
你也可以自己定义自己的Cancellation Point,通过调用pthread_testcancel:
#include <pthread.h>
int pthread_testcancel(void)
返回0表示正常,出错时返回错误值
2. Cancelability Type:指定Cancel的类型。缺省情况下,行为正如我们之前所描述的那样,线程会运行到一个Cancellation Point再Cancel,称之为Deferred Cancellation,对应的常量为PTHREAD_CANCEL_DEFERRED。此外,还支持一种称为PTHREAD_CANCEL_ASYNCHRONOUS的类型。这种情况下,线程会立刻被Cancel,无需得到Cancellation Point。使用pthread_setcanceltype可以改变线程的CancelType属性属性
#include <pthread.h>
int pthread_setcanceltype(int type, int *oldtype)
返回0表示正常,出错时返回错误值
注意:这个函数对当前线程有效,并非设置pthread_attr_t
7 Threads and Signals
pthread_sigmask函数可以阻止Signal的发送:
#include <pthread.h>
int pthread_sigmask(int how, const sigset_t *restrict set, sigset_t *restrict oset)
返回0表示正常,出错时返回错误值
调用sigwait函数可以等待signal的产生:
#include <pthread.h>
int sigwait(const sigset_t *restrict set, int *restrict signop);
返回0表示正常,出错时返回错误值
当signal是pending的情况下,调用sigwait会立刻返回并且把signal从pending list中移走,这样这个signal就不会被调用。为了避免这种行为,可以将pthread_sigmask和sigwait合用,首先用pthread_sigmask在signal产生之前阻止某个signal,然后用sigwait等待这个signal。Sigwait会自动Unblock这个signal,然后在等待结束之后恢复mask。
调用pthread_kill可以给一个线程发送signal:
#include <pthread.h>
int pthread_kill(pthread_t thread, int signo)
返回0表示正常,出错时返回错误值
8 Threads and fork
当线程调用fork的时候,整个进程的地址空间都被copy(严格来说是copy-on-write)到child。所有Mutex / Reader-Writer Lock / Condition Variable的状态都被继承下来。子进程中,只存在一个线程,就是当初调用fork的进程的拷贝。由于不是所有线程都被copy,因此需要将所有的同步对象的状态进行处理。(如果是调用exec函数的话没有这个问题,因为整个地址空间被丢弃了)处理的函数是pthread_atfork:
#include <pthread.h>
int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void));
返回0表示正常,出错时返回错误值
1. Prepare:在fork创建child进程之前,在parent进程中调用。职责是:获得所有的锁
2. Parent:在fork创建child进程之后,但在fork调用返回之前,在parent进程中调用。职责是:释放在prepare中获得的所有的锁
3. Child:在fork创建child进程之后,在fork调用返回值钱,在child进程中调用。职责是:释放在prepare中获得的所有的锁。看起来child和Parent这两个handler做的是重复的工作,不过实际情况不是这样。由于fork会make一份进程地址空间的copy,所以parent和child是在释放各自的锁的copy
9 Threads and I/O
因为文件指针的位置是和进程相关的,所以当不同线程调用lseek、read、write的时候容易造成问题。pread & pwrite函数可以用来解决这个问题。这两个函数会设置文件指针然后读写,作为一个原子操作。