fork 的问题:
- 效率不高。内存要从父进程copy到子进程,子进程共享父进程所有的descriptors,等。虽然实现使用了copy-on-write,但仍然是低效的。
- 调用fork之后,父子进程间进行信息交换要用到 IPC ( Inter-Process Communication,进程间通信)。在调用fork前,从父进程传递信息到子进程是容易的,因为子进程copy父进程的数据空间和所有的descriptors。但是从子进程向父进程返回信息不方便。
线程可解决上述问题,线程的创建时间比进程快10-100倍。一个进程中所有的线程共享相同的内存空间。这样在线程间共享数据非常容易,但也产生了同步的问题。
线程共享的有:
- 全局变量
- 进程函数(Process instructions)
- 打开的文件
- 信号句柄和信号处理函数
- 当前工作目录
- User ID,group ID
每个线程也有自己的:
- 线程ID(类型为 pthread_t 经常为 unsigned int )。在进程中,每一个线程由线程ID唯一标识。
- 一组 registers,包括 program counter 和 stack pointer
- 堆栈(局部变量和返回地址)
- errno
- Signal mask
- 优先级
线程的建立和终止
#include <pthread.h>
int pthread_create(pthread_t *tid, const pthread_attr_t *attr, void *(*func) (void *), void *arg);
返回 0 说明创建成功,返回负值(为 Exxx )说明错误。所有的线程函数都不设置 errno,它们直接返回错误值,这和其它的系统调用(返回-1,设置errno)不同。
线程ID由tid返回。
attr提供线程属性的设置,为空说明使用默认属性。
int pthread_join (pthread_t tid, void ** status);
返回 0 说明成功,返回负值(为 Exxx )说明错误。
等待指定线程结束,类似于waitpid。没有办法等待所有的线程,只能等待指定的线程。如果status不为空,线程的返回值保存到status中。
pthread_t pthread_self (void);
返回线程ID。
int pthread_detach (pthread_t tid);
返回 0 说明成功,返回负值(为 Exxx )说明错误。
一个线程或者是joinable(默认),或者是detached。当joinable线程退出时,它的线程ID和返回值一直保存,直到有其它线程调用pthread_join。但一个detached线程结束后,所有的资源都释放了,它是不能被等待的。如:pthread_detach (pthread_self());
void pthread_exit (void *status);
status不能指向调用线程的局部变量,当线程结束,这些内存会失效。
Thread-Specific Data
系统有两个列表支持Thread-Specific Data。POSIX规定每个进程列表的长度不少于128。一个我们称之为key structures 数组,key structures有如下结构
{
flag; // flag说明这个item是否被使用。默认都是没有使用,当调用pthread_key_create时,找到第一个没有使用的,并返回它的索引(0-127)
destructor ptr; // 函数指针,当对应的pkey array中的指针要释放时调用些函数进行释放
}
另一个我们称之为pkey array,是一个128元素的指针。所有的这些指针初始化为0。如果flag表示这一项被使用,则对应的数据在pkey array中。
当线程结束时,pkey array中的指针调用destructor ptr进行清理工作。
int pthread_once(pthread_once_t *onceptr, void (*init) (void));
返回 0 说明成功,返回负值(为 Exxx )说明错误。保证在一个进程中,指定的init函数只运行一次,可以用来做一些初始化的工作。如:
static pthread_key_t rl_key;
static pthread_once_t rl_once = PTHREAD_ONCE_INIT;
static void readline_once(void)
{
pthread_key_creat(&rl_key, readline_destructor);
}
...
pthread_once(&rl_once, readline_once);
int pthread_key_create(pthread_key_t *keyptr, void (*destructor) (void *value));
返回 0 说明成功,返回负值(为 Exxx )说明错误。
在pkey array中的位置由keyptr返回。destructor设置key structures中的destructor ptr,如果此值不为空,在线程结束时,此函数被调用来对pkey array中对应位置的值进行清理。
void *pthread_getspecific(pthread_key_t key);
指向thread-specific data,可能返回为空。
int pthread_setspecific(pthread_key_t key, const void *value);
返回 0 说明成功,返回负值(为 Exxx )说明错误。设置hread-specific data
int pthread_mutex_lock(pthread_mutex_t * mptr);
int pthread_mutex_unlock(pthread_mutex_t * mptr);
返回 0 说明成功,返回负值(为 Exxx )说明错误。
如果一个mutex变量是静态的,初始化为PTHREAD_MUTEX_INITIALIZER
Condition Variables
在main的主循环中进行sleep,在线程中执行一些操作唤醒主循环,这种情况就要用到 Condition Variables。一个Condition Variables要和mutex变量捆绑使用。
int pthread_cond_wait(pthread_cond_t *cptr, pthread_mutex_t *mptr);
int pthread_cond_signal(pthread_cond_t *cptr);
返回 0 说明成功,返回负值(为 Exxx )说明错误。
如果一个pthread_cond_t变量是静态的,初始化为PTHREAD_COND_INITIALIZER。
pthread_mutex_t ndone_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t ndone_cond = PTHREAD_COND_INITIALIZER;
...
pthread_mutex_lock(&ndone_mutex);
...
pthread_cond_signal(&ndone_cond);
pthread_mutex_unlock(&ndone_mutex);
...
pthread_mutex_lock(&ndone_mutex);
...
pthread_cond_wait (&ndone_cond, &ndone_mutex);
...
pthread_mutex_unlock (&ndone_mutex);
pthread_cond_signal 唤醒一个正在等待cptr的线程。当线程调用pthread_cond_wait时,首先解锁,然后sleep,当被唤醒后,再上锁,然后返回。
int pthread_cond_broadcast (pthread_cond_t * cptr);
返回 0 说明成功,返回负值(为 Exxx )说明错误。唤醒所有等待在cptr上的线程。
int pthread_cond_timedwait (pthread_cond_t * cptr, pthread_mutex_t *mptr, const struct timespec *abstime);
返回 0 说明成功,返回负值(为 Exxx )说明错误。加上一个超时的条件。
注意,这里的时间是绝对时间,如2013.2.3,不是15分钟之类的
struct timeval tv;
struct timespec ts;
if (gettimeofday(&tv, NULL) < 0)
{
err_sys("gettimeofday error");
}
ts.tv_sec = tv.tv_sec + 5; /* 5 seconds in future */
ts.tv_nsec = tv.tv_usec * 1000; /* microsec to nanosec */
pthread_cond_timedwait( ..., &ts);
用绝对时间的好处是当通过信号被唤醒时,时间不会改变