- fork是昂贵的。内存映像要从父进程拷贝到子进程,所有描述字要在子进程中复制等等。目前有的Unix实现使用一种叫做写时拷贝(copy-on-write)的技术,可避免父进程数据空间向子进程的拷贝。尽管有这种优化技术,fork仍然是昂贵的。
- 2. fork子进程后,需要用进程间通信(IPC)在父子进程之间传递信息。Fork之前的信息容易传递,因为子进程从一开始就有父进程数据空间及所有描述字的拷贝。但是从子进程返回信息给父进程需要做更多的工作。
1.线程基础介绍:
- 数据结构:
pthread_t:线程的ID
pthread_attr_t:线程的属性
- 操作函数:
pthread_create():创建一个线程
pthread_exit():终止当前线程
pthread_cancel():中断另外一个线程的运行
pthread_join():阻塞当前的线程,直到另外一个线程运行结束
pthread_attr_init():初始化线程的属性
pthread_attr_setdetachstate():设置脱离状态的属性(决定这个线程在终止时是否可以被结合)
pthread_attr_getdetachstate():获取脱离状态的属性
pthread_attr_destroy():删除线程的属性
pthread_kill():向线程发送一个信号
- 同步函数:
用于 mutex 和条件变量
pthread_mutex_init()初始化互斥锁
pthread_mutex_destroy()删除互斥锁
pthread_mutex_lock():占有互斥锁(阻塞操作)
pthread_mutex_trylock():试图占有互斥锁(不阻塞操作)。即,当互斥锁空闲时,将占有该锁;否则,立即返回。
pthread_mutex_unlock():释放互斥锁
pthread_cond_init():初始化条件变量
pthread_cond_destroy():销毁条件变量
pthread_cond_signal():唤醒第一个调用pthread_cond_wait()而进入睡眠的线程
pthread_cond_wait():等待条件变量的特殊条件发生
Thread-local storage(或者以Pthreads术语,称作线程特有数据):
pthread_key_create():分配用于标识进程中线程特定数据的键
pthread_setspecific():为指定线程特定数据键设置线程特定绑定
pthread_getspecific():获取调用线程的键绑定,并将该绑定存储在 value 指向的位置中
pthread_key_delete():销毁现有线程特定数据键
pthread_attr_getschedparam();获取线程优先级
pthread_attr_setschedparam();设置线程优先级
2.概念:
3.线程定义
#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.线程的创建
#include <pthread.h> int pthread_create(
pthread_t*restrict tidp,
constpthread_attr_t*restrict attr,
void*(*start_rtn)(void*),void*restrict arg);
- pthread_t *restrict tidp:返回最后创建出来的Thread的Thread ID
- const pthread_attr_t *restrict attr:指定线程的Attributes,后面会讲道,现在可以用NULL
- void *(*start_rtn)(void *):指定线程函数指针,该函数返回一个void *,参数也为void*
- void *restrict arg:传入给线程函数的参数
- 返回错误值。
5.线程的退出
- exit, _Exit, _exit用于中止当前进程,而非线程
- 中止线程可以有三种方式:
a. 在线程函数中return
- pthread_exit和pthread_join函数的用法:
#include <pthread.h> void pthread_exit(void*rval_ptr); int pthread_join(pthread_t thread,void**rval_ptr);
- 一个Thread可以要求另外一个Thread被Cancel,通过调用pthread_cancel函数:
#include <pthread.h> void pthread_cancel(pthread_t tid)
该函数会使指定线程如同调用了pthread_exit(PTHREAD_CANCELLED)。不过,指定线程可以选择忽略或者进行自己的处理,在后面会讲到。此外,该函数不会导致Block,只是发送Cancel这个请求。
- 线程可以安排在它退出的时候,某些函数自动被调用,类似atexit()函数。需要调用如下函数:
#include <pthread.h> void pthread_cleanup_push(void(*rtn)(void*),void*arg);
void pthread_cleanup_pop(int execute);
这两个函数维护一个函数指针的Stack,可以把函数指针和函数参数值push/pop。执行的顺序则是从栈顶到栈底,也就是和push的顺序相反。
void*thread_func(void*arg)
{
pthread_cleanup_push(cleanup,“handler”) // do something Pthread_cleanup_pop();
return((void*));
}
- 进程函数和线程函数的相关性:
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
|
请求非正常退出
|
- 缺省情况下,一个线程A的结束状态被保存下来直到pthread_join为该线程被调用过,也就是说即使线程A已经结束,只要没有线程B调用 pthread_join(A),A的退出状态则一直被保存。而当线程处于Detached状态之时,当线程退出的时候,其资源可以立刻被回收,那么这个退出状态也丢失了。在这个状态下,无法为该线程调用pthread_join函数。我们可以通过调用pthread_detach函数来使指定线程进入 Detach状态:
#include <pthread.h>
int pthread_detach(pthread_t tid);
6.线程同步
- 互斥量:Mutex
各个现成向同一个文件顺序写入数据,最后得到的结果是不可想象的。所以用互斥锁来保证一段时间内只有一个线程在执行一段代码。
#include <pthread.h> int pthread_mutex_init(
pthread_mutex_t*restrict mutex,
constpthread_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。
#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);
void reader_function (void);
void writer_function (void);
char buffer;
int buffer_has_item=;
pthread_mutex_t mutex;
struct timespec delay;
void main (void)
{
pthread_t reader;
/* 定义延迟时间*/
delay.tv_sec =;
delay.tv_nec =;
/* 用默认属性初始化一个互斥锁对象*/
pthread_mutex_init (&mutex,NULL);
pthread_create(&reader, pthread_attr_default,(void*)&reader_function), NULL);
writer_function();
}
void writer_function (void){
while(){
/* 锁定互斥锁*/
pthread_mutex_lock (&mutex);
if(buffer_has_item==){
buffer=make_new_item();
buffer_has_item=;
}
/* 打开互斥锁*/
pthread_mutex_unlock(&mutex);
pthread_delay_np(&delay);
}
}
void reader_function(void){
while(){
pthread_mutex_lock(&mutex);
if(buffer_has_item==){
consume_item(buffer);
buffer_has_item=;
}
pthread_mutex_unlock(&mutex);
pthread_delay_np(&delay);
}
}
需要注意的是在使用互斥锁的过程中很有可能会出现死锁:两个线程试图同时占用两个资源,并按不同的次序锁定相应的互斥锁,例如两个线程都需要锁定互斥锁1和互斥锁2,a线程先锁定互斥锁1,b 线程先锁定互斥锁2,这时就出现了死锁。此时我们可以使用函数 pthread_mutex_trylock,它是函数pthread_mutex_lock的非阻塞版本,当它发现死锁不可避免时,它会返回相应的信息,程序员可以针对死锁做出相应的处理。另外不同的互斥锁类型对死锁的处理不一样,但最主要的还是要程序员自己在程序设计注意这一点
- 读写锁:Reader-Writer Locks
#include <pthread.h> int pthread_rwlock_init(
pthread_rwlock_t*restrict rwlock,
constpthread_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:获得读锁
- Conditional Variable:条件变量
互斥锁一个明显的缺点是它只有两种状态:锁定和非锁定。而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来,条件变量被用来进行线程间的同步。
#include <pthread.h> int pthread_cond_init(
pthread_cond_t*restrict cond,
constpthread_condxattr_t*restrict attr) int pthread_cond_destroy(pthread_cond_t*cond);
#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,
conststruct timespec *restrict timeout);
pthread_mutex_t count_lock;
pthread_cond_t count_nonzero;
unsigned count;
decrement_count (){
pthread_mutex_lock (&count_lock);
while(count==)
pthread_cond_wait(&count_nonzero,&count_lock);
count=count -;
pthread_mutex_unlock (&count_lock);
}
increment_count(){
pthread_mutex_lock(&count_lock);
if(count==)
pthread_cond_signal(&count_nonzero);
count=count+;
pthread_mutex_unlock(&count_lock);
}
struct timespec {
time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
注意timespec的时间是绝对时间而非相对时间,因此需要先调用gettimeofday函数获得当前时间,再转换成timespec结构,加上偏移量。
#include <pthread.h> int pthread_cond_signal(pthread_cond_t*cond); int pthread_cond_broadcast(pthread_cond_t*cond);
7.线程属性
- 线程属性设置
- 绑定
#include <pthread.h>
pthread_attr_t attr;
pthread_t tid;
/*初始化属性值,均设为默认值*/
pthread_attr_init(&attr);
pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
pthread_create(&tid,&attr,(void*) my_function, NULL);
- 线程分离状态
线程的分离状态决定一个线程以什么样的方式来终止自己。非分离的线程终止时,其线程ID和退出状态将保留,直到另外一个线程调用 pthread_join.分离的线程在当它终止时,所有的资源将释放,我们不能等待它终止。
- 4.优先级
它存放在结构sched_param中。用函数pthread_attr_getschedparam和函数 pthread_attr_setschedparam进行存放,一般说来,我们总是先取优先级,对取得的值修改后再存放回去。下面即是一段简单的例子。
#include <pthread.h>
#include <sched.h>
pthread_attr_t attr;pthread_t tid;
sched_param param;
int newprio=;
/*初始化属性*/
pthread_attr_init(&attr);
/*设置优先级*/
pthread_attr_getschedparam(&attr,¶m);
param.sched_priority=newprio;
pthread_attr_setschedparam(&attr,¶m);
pthread_create(&tid,&attr,(void*)myfunction, myarg);