线程
一个进程中的所有线程共享为进程分配的地址空间。所以进程地址空间中的代码段和数据段都是共享的。
如果定义一个函数在各个线程中都可以调用,定义一个全部变量,在各个线程中都可以访问到。
各线程共享资源
- 数据段和文本(代码)段
- 对全局变量的访问
- 文件描述符
- 每种信号的处理方式(SIG_IGN,SIG_DFL或自定义的信号处理函数)
- 当前的工作目录
- 用户的id和组id
各线程私有资源
1. 线程id
2. 上下文,包括寄存器的值,程序计数器和栈指针
3. 栈空间
4. error变量
5. 信号屏蔽字
6. 调度优先级
线程操作
创建线程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
线程终止
终止线程的三种情况
- 调用pthread_exit函数退出
- 调用pthread_cancel函数取消该线程
- 创建线程的进程退出或整个函数结束
- 其中一个线程执行了exec类函数执行了新的进程
void pthread_exit(void *retval);
该参数用来保存线程退出状态
3.线程等待
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
调用该函数的线程将挂起等待,直到id的线程终止。当函数是返回时,处于等待的线程资源被回收。
成功返回0,失败返回错误码
线程的同步与互斥
一、互斥锁(mutex)
锁机制是同一时刻只允许一个线程执行一个关键部分的代码
有两种方法创建互斥锁,静态方式和动态方式。POSIX定义了一个宏PTHREAD_MUTEX_INITIALIZER来静态初始化互斥锁,方法如下: pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER; 在LinuxThreads实现中,pthread_mutex_t是一个结构,而PTHREAD_MUTEX_INITIALIZER则是一个结构常量。
动态方式是采用pthread_mutex_init()函数来初始化互斥锁,API定义如下: int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr) 其中mutexattr用于指定互斥锁属性(见下),如果为NULL则使用缺省属性。
pthread_mutex_destroy ()用于注销一个互斥锁,API定义如下: int pthread_mutex_destroy(pthread_mutex_t *mutex) 销毁一个互斥锁即意味着释放它所占用的资源,且要求锁当前处于开放状态。由于在Linux中,互斥锁并不占用任何资源,因此LinuxThreads中的 pthread_mutex_destroy()除了检查锁状态以外(锁定状态则返回EBUSY)没有其他动作。
1.初始化锁
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutex_attr_t *mutexattr);
其中参数 mutexattr 用于指定锁的属性(见下),如果为NULL则使用缺省属性。
互斥锁的属性在创建锁的时候指定,在LinuxThreads实现中仅有一个锁类型属性,不同的锁类型在试图对一个已经被锁定的互斥锁加锁时表现不同。当前有四个值可供选择:
(1)PTHREAD_MUTEX_TIMED_NP,这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。
(2)PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。
(3)PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样就保证当不允许多次加锁时不会出现最简单情况下的死锁。
(4)PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,动作最简单的锁类型,仅等待解锁后重新竞争
2.阻塞加锁
int pthread_mutex_lock(pthread_mutex *mutex);
3.非阻塞加锁
int pthread_mutex_trylock(pthread_mutex_t *mutex);
该函数语义与 pthread_mutex_lock() 类似,不同的是在锁已经被占据时返回 EBUSY 而不是挂起等待。
4.解锁(要求锁是lock状态,并且由加锁线程解锁)
int pthread_mutex_unlock(pthread_mutex *mutex);
5.销毁锁(此时锁必需unlock状态,否则返回EBUSY)
int pthread_mutex_destroy(pthread_mutex *mutex);
二、条件变量(cond)
条件变量是利用线程间共享全局变量进行同步的一种机制。条件变量上的基本操作有:触发条件(当条件变为true时);等待条件,挂起线程直到其他线程触发条件。
1.初始化条件变量
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);
尽管POSIX标准中为条件变量定义了属性,但在Linux中没有实现,因此cond_attr值通常为NULL,且被忽略。
2.有两个等待函数
(1)无条件等待
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
(2)计时等待
int pthread_cond_timewait(pthread_cond_t *cond, pthread_mutex_t *mutex,const timespec *abstime);
如果在给定时刻前条件没有满足,则返回ETIMEOUT,结束等待,其中abstime以与time()系统调用相同意义的绝对时间形式出现,0表示格林尼治时间1970年1月1日0时0分0秒。
无论哪种等待方式,都必须和一个互斥锁配合,以防止多个线程同时请求(用 pthread_cond_wait() 或 pthread_cond_timedwait() 请求)竞争条件(Race Condition)。mutex互斥锁必须是普通锁(PTHREAD_MUTEX_TIMED_NP)或者适应锁(PTHREAD_MUTEX_ADAPTIVE_NP),且在调用pthread_cond_wait()前必须由本线程加锁(pthread_mutex_lock()),而在更新条件等待队列以前,mutex保持锁定状态,并在线程挂起进入等待前解锁。在条件满足从而离开pthread_cond_wait()之前,mutex将被重新加锁,以与进入pthread_cond_wait()前的加锁动作对应。
3.激发条件
(1)激活一个等待该条件的线程(存在多个等待线程时按入队顺序激活其中一个)
int pthread_cond_signal(pthread_cond_t *cond);
(2)激活所有等待线程
int pthread_cond_broadcast(pthread_cond_t *cond);
4.销毁条件变量
int pthread_cond_destroy(pthread_cond_t *cond);
只有在没有线程在该条件变量上等待的时候才能销毁这个条件变量,否则返回EBUSY
说明:
1. pthread_cond_wait 自动解锁互斥量(如同执行了pthread_unlock_mutex),并等待条件变量触发。这时线程挂起,不占用CPU时间,直到条件变量被触发(变量为ture)。在调用 pthread_cond_wait之前,应用程序必须加锁互斥量。pthread_cond_wait函数返回前,自动重新对互斥量加锁(如同执行了pthread_lock_mutex)。
2. 当调用pthread_cond_wait()互斥量的解锁和在条件变量挂起都是自动进行的。因此,在条件变量被触发前,所有的线程都要对互斥量加锁,这种机制可保证在线程加锁互斥量和进入等待条件变量的期间,条件变量不被触发。条件变量要和互斥量相联结,以避免出现条件竞争----一个线程预备等待一个条件变量,当它真正进入等待之前另外一个线程恰好触发了该条件(条件满足信号有可能在测试条件和调用调用pthread_cond_wait函数(block)之间发出),从而造成无限制的等待。
3. 条件变量函数不是异步信号安全的,不应当在信号处理程序中进行调用。特别要注意,如果在信号处理程序中调用 pthread_cond_signal 或 pthread_cond_boardcast 函数,可能导致调用线程死锁