当先锋百科网

首页 1 2 3 4 5 6 7

转自:
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_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_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结构定义如下:

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

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

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函数可以用来解决这个问题。这两个函数会设置文件指针然后读写,作为一个原子操作。