分类: LINUX
2013-06-07 10:44:48
线程创建与关闭
在Unix操作系统中,一个进程可以同时运行多个线程,线程由线程ID、寄存器值集合、栈、信号掩码(signal mask)、错误标示符(errno)、线程私有数据构成。同一进程中的多个线程共享文本片段、程序全局存储区、堆存储区、栈存储区、文件描述符。
线程的创建可以使用pthread_create函数,其函数原型为
#include int pthread_create(pthread_t *tidp, const pthread_attr_t *attr, void *(*start_rtn) (void *), void *arg);
参数tidp用于存储线程ID,参数attr标明线程属性。当线程启动时,线程函数start_rtn被调用,其参数为art。如果需要向线程函数中传递多个参数,可以将参数存储于结构体中。新建线程将继承调用线程的信号掩码,并清空悬而未决的信号。与创建进程类似,我们无法确保子线程与主线程的调用顺序。
线程属性由结构体pthread_attr_t定义,在函数pthread_create中使用。使用pthread_attr_t结构体变量前需要通过pthread_attr_init函数初始化,使用结束后需要通过pthread_attr_destory函数释放其所占用的资源。pthread_attr_t结构体对程序是不透明的,我们仅能够使用有限的函数来对线程属性进行操作,而不能通过直接修改结构体中相关域来修改线程属性。与线程属性相关的函数定义及用途如表1所示
函数原型 | 用途 |
int pthread_attr_getdetachstate( const pthread_attr_t *attr, int *detachstate); | 获取线程分离状态,PTHREAD_CREATE_DETACHED 或 PTHREAD_CREATE_JOINABLE |
int pthread_attr_setdetachstate( pthread_attr_t *attr, int detachstate); | 设置线程分离状态,PTHREAD_CREATE_DETACHED 或 PTHREAD_CREATE_JOINABLE |
int pthread_attr_getstack( const pthread_attr_t *attr, void **stackaddr, size_t *stacksize); | 获取线程栈地址 |
int pthread_attr_setstack( pthread_attr_t *attr, void *stackaddr, size_t *stacksize); | 设置线程栈地址 |
int pthread_attr_getstacksize( const pthread_attr_t *attr, size_t *stacksize); | 获取线程栈大小 |
int pthread_attr_setstacksize( pthread_attr_t *attr, size_t stacksize); | 设置线程栈大小 |
int pthread_attr_getguardsize( const pthread_attr_t *attr, size_t *guardsize); | 获取栈溢出检查值大小,当值为0时,系统不检查栈溢出状态 |
int pthread_attr_setguardsize( pthread_attr_t *attr, size_t guardsize); | 设置栈溢出检查值大小,当值为0时,系统不检查栈溢出状态 |
int pthread_getconcurrency(void); | 获取线程最大并行数 |
int pthread_setconcurrency(int level); | 设置线程最大并行数 |
表1. 线程属性相关函数
如果需要退出线程,可以在线程处理函数中return,或者调用pthread_exit函数。如果一个线程对另一个线程执行pthread_cancel操作,且该线程没有忽略cancel状态,则该线程被取消,退出该线程。pthread_cleanup_push函数可以建立栈并将指定函数入栈,栈中的函数将在线程异常退出时被调用。相应的出栈函数为pthread_cleanup_pop。这里的异常退出指:
线程可以通过调用pthread_setcancelstate函数来指定接收到cancel命令时的行为,其值可以为PTHREAD_CANCEL_ENABLE(立即处理)或PTHREAD_CANCEL_DISABLE(不立即处理,该消息悬而未决)。调用pthread_testcancel函数时,如果当前状态有cancel消息悬而未决,则线程立即退出,否则不产生任何影响。
如果一个线程需要等待另一个线程结束,可以使用pthread_join函数。其原型为:
int pthread_join(pthread_t thread, void **rval_ptr);
被等待的线程线程结束时,线程返回值保存在参数rval_ptr指定的内存单元中。调用pthread_join函数时,线程将被分离(detached),线程结束时,线程终止信息(thread's termination status)将被立即收回。pthread_detach函数可以将指定线程分离。
线程同步
线程的同步通过互斥加锁操作来实现。互斥操作又分为互斥锁(mutexes)、读写锁(Read-Write Locks)、条件锁(Condition Variables)。锁可以是递归锁,也可以是非递归锁,这一点由锁的属性决定。当一个线程多次试图对同一个非递归锁加锁时,会出现死锁现象。线程将无限期的阻塞下去。我们可以使用trylock系列函数尝试加锁,当加锁失败时,该函数并不会进入阻塞状态,能够避免死锁的出现。
与锁有关的函数具有统一的命名规则,便于记忆与使用。相关函数原型为
#include //初始化系列 int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr); int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr); int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr); //销毁系列 int pthread_mutex_destory(pthread_mutex_t *mutex); int pthread_rwlock_destory(pthread_rwlock_t *mutex); int pthread_cond_destory(pthread_cond_t *mutex); //加锁 int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_rwlock_rdlock(pthread_rwlock_t *mutex); int pthread_rwlock_wrlock(pthread_rwlock_t *mutex); int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); //解锁 int pthread_mutex_unlock(pthread_mutex_t *mutex); int pthread_rwlock_unlock(pthread_rwlock_t *mutex); int pthread_cond_signal(pthread_cond_t *cond); int pthread_cond_broadcast(pthread_cond_t *cond); //非阻塞加锁,加锁成功时返回0,失败时返回非0值。 int pthread_mutex_trylock(pthread_mutex_t *mutex); int pthread_rwlock_tryrdlock(pthread_rwlock_t *mutex); int pthread_rwlock_trywrlock(pthread_rwlock_t *mutex); int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex);
读写锁中的读锁可以被多次加锁,而不产生阻塞。写锁仅能被加锁一次,当写锁已经被加锁时,再次执行加锁操作将导致阻塞。使用条件锁之前需要先定义一个互斥锁,并对互斥锁加锁。条件锁被加锁时,锁定的互斥锁可以保证条件的改变一定发生在条件锁加锁后,以避免出现先改变条件,后加锁的情况。条件锁加锁成功后,互斥锁仍然为已锁定状态。pthread_cond_signal函数可以唤醒一个被指定条件锁阻塞的线程,pthread_cond_broadcast函数将唤醒全部被指定条件锁阻塞的线程。
线程锁既可以用于同步线程,也可以用于同步进程。同步进程时,需要指定锁属性为PTHREAD_PROCESS_SHARED。相关函数原型为
//获取共享状态,线程共享或进程共享 int pthread_mutexattr_getpshared(const pthread_mutexattr_t *attr, int *pshared); int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *attr, int *pshared); int pthread_condattr_getpshared(const pthread_condattr_t *attr, int *pshared); //设置共享状态 int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared); int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared); int pthread_condattr_setpshared(pthread_condattr_t *attr, int pshared);
可以通过向pthread_mutexattr_settype函数传递PTHREAD_MUTEX_RECURSIVE参数来指定互斥锁为递归锁。递归锁可以在同一线程中被加锁多次,但相应的,递归锁必须被解锁同样次数。递归锁仅适用于一些特殊情况,如果有其他方案,应当尽量避免使用递归锁。函数原型为
int pthread_mutexattr_gettype(const pthread_mutexattr_t *attr, int *type); int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type);
如果一个函数可以在多个线程中被同时调用,而保持数据正确,则称该函数为线程安全的。一般而言,如果函数中需要向静态存储空间中保存数据,则该函数非线程安全函数。
实际应用中,线程可能需要动态申请一段内存空间,将其中数据设置为线程私有,这时可以使用pthread_setspecific函数。在调用pthread_setspecific之前,需要先建立一个连接点,类型为pthread_key_t。堆中的私有数据空间与key相关联。相关函数原型为
int pthread_key_create(pthread_key_t *keyp, void (*destructor) (void*)); int pthread_key_delete(pthread_key_t *key);
int pthread_getspecific(pthread_key_t key); int pthread_setspecific(pthread_key_t key, const void *value);
当pthread_getspecific函数返回NULL时,堆中没有与key相关联的内存单元。
如果某一函数需要在该进程的所有线程中仅被执行一次,可使用pthread_once函数。其原型为
pthread_once_t initflag = PTHREAD_ONCE_INIT; int pthread_once(pthread_once_t *initflag, void (*initfn) (void));
线程的使用
每一个线程具有不同的信号掩码(signal mask)。进程中产生一个信号后,该信号将被发送至任意一个没有阻塞该信号的线程。当需要在线程中设置信号掩码时,可以使用pthread_sigmask函数,其原型与sigprocmask函数类似。
当线程需要等待信号时,可以使用sigwait函数,其原型为
int sigwait(const sigset_t *set, int *signop);
参数set指定线程希望等待的信号,当接收到信号时,参数signop为接收到的信号ID。
当一个线程需要向指定线程发送信号时,可以使用pthread_kill函数,其原型为
int pthread_kill(pthread_t thread, int signo);
如果在一个线程中创建了一个新的进程,该子进程将复制父进程中的所有数据,子进程中,只包含一个线程,该线程由创建进程的线程复制而来。由于子进程包含父进程的所有数据,而子进程无法知道哪些锁已被锁定。因此,子进程需要释放已锁定的锁锁占用的系统资源。pthrea_atfork函数可以指定线程调用fork函数前后,父进程与子进程必须执行的操作。其函数原型为
int pthread_atfork( void (*prepare) (void), void (*parent) (void), void (*child) (void));
prepare函数将在fork执行前调用,fork执行后,父进程调用parent函数,子进程调用child函数。我们可以在这里为子进程清除父进程锁使用的锁。