(一) 理解Linux下进程的结构 linux
Linux下一个进程在内存里有三部份的数据,就是“数据段”,“堆栈段”和“代码段”,其实学过汇编语言的人必定知道,通常的CPU象I386,都有上述三种段寄存器,以方便操做系统的运行。“代码段”,顾名思义,就是存放了程序代码的数据,假如机器中有数个进程运行相同的一个程序,那么它们就可使用同一个代码段。 程序员
堆栈段存放的就是子程序的返回地址、子程序的参数以及程序的局部变量。而数据段则存放程序的全局变量,常数以及动态数据分配的数据空间(好比用malloc之类的函数取得的空间)。这其中有许多细节问题,这里限于篇幅就很少介绍了。系统若是同时运行数个相同的程序,它们之间就不能使用同一个堆栈段和数据段。 编程
(二) 如何使用fork 数组
在Linux下产生新的进程的系统调用就是fork函数,这个函数名是英文中“分叉”的意思。为何取这个名字呢?由于一个进程在运行中,若是使用了fork,就产生了另外一个进程,因而进程就“分叉”了,因此这个名字取得很形象。下面就看看如何具体使用fork,这段程序演示了使用fork的基本框架: 安全
void main(){ 数据结构
int i; 多线程
if ( fork() == 0 ) { 并发
/* 子进程程序 */ app
for ( i = 1; i " ); 框架
fgets( command, 256, stdin );
command[strlen(command)-1] = 0;
if ( fork() == 0 ) {
/* 子进程执行此命令 */
execlp( command, command );
/* 若是exec函数返回,代表没有正常执行命令,打印错误信息*/
perror( command );
exit( errorno );
}
else {
/* 父进程, 等待子进程结束,并打印子进程的返回值 */
wait ( &rtn );
printf( " child process return %d\n",. rtn );
}
}
}
此程序从终端读入命令并执行之,执行完成后,父进程继续等待从终端读入命令。熟悉DOS和WINDOWS系统调用的朋友必定知道DOS/WINDOWS也有exec类函数,其使用方法是相似的,但DOS/WINDOWS还有spawn类函数,由于DOS是单任务的系统,它只能将“父进程”驻留在机器内再执行“子进程”,这就是spawn类的函数。WIN32已是多任务的系统了,但还保留了spawn类函数,WIN32中实现spawn函数的方法同前述UNIX中的方法差很少,开设子进程后父进程等待子进程结束后才继续运行。UNIX在其一开始就是多任务的系统,因此从核心角度上讲不须要spawn类函数。另外,有一个更简单的执行其它程序的函数system,它是一个较高层的函数,实际上至关于在SHELL环境下执行一条命令,而exec类函数则是低层的系统调用。
(四) Linux的进程与Win32的进程/线程有何区别
熟悉WIN32编程的人必定知道,WIN32的进程管理方式与UNIX上有着很大区别,在UNIX里,只有进程的概念,但在WIN32里却还有一个“线程”的概念,那么
UNIX和WIN32在这里究竟有着什么区别呢?
UNIX里的fork是七十年代UNIX早期的开发者通过长期在理论和实践上的艰苦探索后取得的成果,一方面,它使操做系统在进程管理上付出了最小的代价,另外一方面,又为程序员提供了一个简洁明了的多进程方法。
WIN32里的进程/线程是继承自OS/2的。在WIN32里,“进程”是指一个程序,而“线程”是一个“进程”里的一个执行“线索”。从核心上讲,WIN32的多进程与
UNIX并没有多大的区别,在WIN32里的线程才至关于UNIX的进程,是一个实际正在执行的代码。可是,WIN32里同一个进程里各个线程之间是共享数据段的。这才是与UNIX的进程最大的不一样。
下面这段程序显示了WIN32下一个进程如何启动一个线程:(请注意,这是个终端方式程序,没有图形界面)
int g;
DWORD WINAPI ChildProcess( LPVOID lpParameter ){
int i;
for ( i = 1; i
pthread 解读
Posix线程编程指南(1)
内容:
1、 线程建立
2、线程取消
线程建立与取消
杨沙洲(pubb@163.net)
2001 年 10 月
这是一个关于Posix线程编程的专栏。做者在阐明概念的基础上,将向您详细讲述Posix线程库API。本文是第一篇将向您讲述线程的建立与取消。
1、 线程建立
1.1 线程与进程
相对进程而言,线程是一个更加接近于执行体的概念,它能够与同进程中的其余线程共享数据,但拥有本身的栈空间,拥有独立的执行序列。在串行程序基础上引入
线程和进程是为了提升程序的并发度,从而提升程序运行效率和响应时间。
线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源的管理和保护;而进程正相反。同时,线程适合于在SMP机器上运行,而进程则能够跨机器迁移。
1.2 建立线程
POSIX经过pthread_create()函数建立线程,API定义以下:
int pthread_create(pthread_t * thread, pthread_attr_t * attr,
void * (*start_routine)(void *), void * arg)
与fork()调用建立一个进程的方法不一样,pthread_create()建立的线程并不具有与主线程(即调用pthread_create()的线程)一样的执行序列,而是使其运行start_routine(arg)函数。thread返回建立的线程ID,而attr是建立线程时设置的线程属性(见下)。pthread_create()的返回值表示线程建立是否成功。尽管arg是void*类型的变量,但它一样能够做为任意类型的参数传给start_routine()函数;同时,start_routine()能够返回一个void*类型的返回值,而这个返回值也能够是其余类型,并由pthread_join()获取。
1.3 线程建立属性
pthread_create()中的attr参数是一个结构指针,结构中的元素分别对应着新线程的运行属性,主要包括如下几项:
__detachstate,表示新线程是否与进程中其余线程脱离同步,若是置位则新线程不能用pthread_join()来同步,且在退出时自行释放所占用的资源。缺省为PTHREAD_CREATE_JOINABLE状态。这个属性也能够在线程建立并运行之后用pthread_detach()来设置,而一旦设置为PTHREAD_CREATE_DETACH状态(不管是建立时设置仍是运行时设置)则不能再恢复到PTHREAD_CREATE_JOINABLE状态。
__schedpolicy,表示新线程的调度策略,主要包括SCHED_OTHER(正常、非实时)、SCHED_RR(实时、轮转法)和SCHED_FIFO(实时、先入先出)三种,缺省为SCHED_OTHER,后两种调度策略仅对超级用户有效。运行时能够用过pthread_setschedparam()来改变。
__schedparam,一个struct sched_param结构,目前仅有一个sched_priority整型变量表示线程的运行优先级。这个参数仅当调度策略为实时(即SCHED_RR或SCHED_FIFO)时才有效,并能够在运行时经过pthread_setschedparam()函数来改变,缺省为0。
__inheritsched,有两种值可供选择:PTHREAD_EXPLICIT_SCHED和PTHREAD_INHERIT_SCHED,前者表示新线程使用显式指定调度策略和调度参数(即attr中的值),
然后者表示继承调用者线程的值。缺省为PTHREAD_EXPLICIT_SCHED。
__scope,表示线程间竞争CPU的范围,也就是说线程优先级的有效范围。POSIX的标准中定义了两个值:PTHREAD_SCOPE_SYSTEM和PTHREAD_SCOPE_PROCESS,前者表示与系统中全部线程一块儿竞争CPU时间,后者表示仅与同进程中的线程竞争CPU。目前LinuxThreads仅实现了PTHREAD_SCOPE_SYSTEM一值。
pthread_attr_t结构中还有一些值,但不使用pthread_create()来设置。
为了设置这些属性,POSIX定义了一系列属性设置函数,包括pthread_attr_init()、pthread_attr_destroy()和与各个属性相关的pthread_attr_get---/pthread_at
tr_set---函数。
1.4 线程建立的Linux实现
咱们知道,Linux的线程实现是在核外进行的,核内提供的是建立进程的接口do_fork()。内核提供了两个系统调用__clone()和fork(),最终都用不一样的参数调用do_fork()核内API。固然,要想实现线程,没有核心对多进程(实际上是轻量级进程)共享数据段的支持是不行的,所以,do_fork()提供了不少参数,包括CLONE_VM(共享内存空间)、CLONE_FS(共享文件系统信息)、CLONE_FILES(共享文件描述符表)、CLONE_SIGHAND(共享信号句柄表)和CLONE_PID(共享进程ID,仅对核内进程,即0号进程有效)。当使用fork系统调用时,内核调用do_fork()不使用任何共享属性,进程拥有独立的运行环境,而使用pthread_create()来建立线程时,则最终设置了全部这些属性来调用__clone(),而这些参数又所有传给核内的do_fork(),从而建立的"进程"拥有共享的运行环境,只有栈是独立的,由__clone()传入。
Linux线程在核内是以轻量级进程的形式存在的,拥有独立的进程表项,而全部的建立、同步、删除等操做都在核外pthread库中进行。pthread库使用一个管理线程(__pthread_manager(),每一个进程独立且惟一)来管理线程的建立和终止,为线程分配线程ID,发送线程相关的信号(好比Cancel),而主线程(pthread_create())的调用者则经过管道将请求信息传给管理线程。
2、线程取消
2.1 线程取消的定义
通常状况下,线程在其主体函数退出的时候会自动终止,但同时也能够由于接收到另外一个线程发来的终止(取消)请求而强制终止。
2.2 线程取消的语义
线程取消的方法是向目标线程发Cancel信号,但如何处理Cancel信号则由目标线程本身决定,或者忽略、或者当即终止、或者继续运行至Cancelation-point(取消点),由不一样的Cancelation状态决定。
线程接收到CANCEL信号的缺省处理(即pthread_create()建立线程的缺省状态)是继续运行至取消点,也就是说设置一个CANCELED状态,线程继续运行,只有运行至Cancelation-point的时候才会退出。
2.3 取消点
根据POSIX标准,pthread_join()、pthread_testcancel()、pthread_cond_wait()、pthread_cond_timedwait()、sem_wait()、sigwait()等函数以及read()、write()等会引发阻塞的系统调用都是Cancelation-point,而其余pthread函数都不会引发Cancelation动做。可是pthread_cancel的手册页声称,因为LinuxThread库与C库结合得很差,于是目前C库函数都不是Cancelation-point;但CANCEL信号会使线程从阻塞的系统调用中退出,并置EINTR错误码,所以能够在须要做为Cancelation-point的系统调用先后调用pthread_testcancel(),从而达到POSIX标准所要求的目标,即以下代码段:
pthread_testcancel();
retcode = read(fd, buffer, length);
pthread_testcancel();
2.4 程序设计方面的考虑
若是线程处于无限循环中,且循环体内没有执行至取消点的必然路径,则线程没法由外部其余线程的取消请求而终止。所以在这样的循环体的必经路径上应该加入pthread_testcancel()调用。
2.5 与线程取消相关的pthread函数
int pthread_cancel(pthread_t thread)
发送终止信号给thread线程,若是成功则返回0,不然为非0值。发送成功并不意味着thread会终止。
int pthread_setcancelstate(int state, int *oldstate)
设置本线程对Cancel信号的反应,state有两种值:PTHREAD_CANCEL_ENABLE(缺省)和PTHREAD_CANCEL_DISABLE,分别表示收到信号后设为CANCLED状态和忽略CANCEL信号继续运行;old_state若是不为NULL则存入原来的Cancel状态以便恢复。
int pthread_setcanceltype(int type, int *oldtype)
设置本线程取消动做的执行时机,type由两种取值:PTHREAD_CANCEL_DEFFERED和PTHREAD_CANCEL_ASYCHRONOUS,仅当Cancel状态为Enable时有效,分别表示收到信号后继续运行至下一个取消点再退出和当即执行取消动做(退出);oldtype若是不为NULL则存入运来的取消动做类型值。
void pthread_testcancel(void)
检查本线程是否处于Canceld状态,若是是,则进行取消动做,不然直接返回。
posix线程编程指南(2)
内容:
一. 概念及做用
二. 建立和注销
三. 访问
四. 使用范例
关于做者
相关内容:
(1) 线程建立与取消
线程私有数据
杨沙洲(pubb@163.net)
2001 年 10 月
这是一个关于Posix线程编程的专栏。做者在阐明概念的基础上,将向您详细讲述Posix线程库API。本文是第二篇将向您讲述线程的私有数据。
一. 概念及做用
在单线程程序中,咱们常常要用到"全局变量"以实现多个函数间共享数据。在多线程环境下,因为数据空间是共享的,所以全局变量也为全部线程所共有。但有时应用程序设计中有必要提供线程私有的全局变量,仅在某个线程中有效,但却能够跨多个函数访问,好比程序可能须要每一个线程维护一个链表,而使用相同的函数操做,最简单的办法就是使用同名而不一样变量地址的线程相关数据结构。这样的数据结构能够由Posix线程库维护,称为线程私有数据(Thread-specificData,或TSD)。
二. 建立和注销
Posix定义了两个API分别用来建立和注销TSD:
int pthread_key_create(pthread_key_t *key, void (*destr_function) (void *))
该函数从TSD池中分配一项,将其值赋给key供之后访问使用。若是destr_function不为空,在线程退出(pthread_exit())时将以key所关联的数据为参数调用destr_function(),以释放分配的缓冲区。
不论哪一个线程调用pthread_key_create(),所建立的key都是全部线程可访问的,但各个线程可根据本身的须要往key中填入不一样的值,这就至关于提供了一个同名而不一样值的全局变量。在LinuxThreads的实现中,TSD池用一个结构数组表示:
static struct pthread_key_struct pthread_keys[PTHREAD_KEYS_MAX] = { { 0, NULL } };
建立一个TSD就至关于将结构数组中的某一项设置为"in_use",并将其索引返回给*key,而后设置destructor函数为destr_function。
注销一个TSD采用以下API:
int pthread_key_delete(pthread_key_t key)
这个函数并不检查当前是否有线程正使用该TSD,也不会调用清理函数(destr_function),而只是将TSD释放以供下一次调用pthread_key_create()使用。在LinuxThreads中,它还会将与之相关的线程数据项设为NULL(见"访问")。
三. 访问
TSD的读写都经过专门的Posix Thread函数进行,其API定义以下:
int pthread_setspecific(pthread_key_t key, const void *pointer)
void * pthread_getspecific(pthread_key_t key)
写入(pthread_setspecific())时,将pointer的值(不是所指的内容)与key相关联,而相应的读出函数则将与key相关联的数据读出来。数据类型都设为void
*,所以能够指向任何类型的数据。
在LinuxThreads中,使用了一个位于线程描述结构(_pthread_descr_struct)中的二维void *指针数组来存放与key关联的数据,数组大小由如下几个宏来讲明:
#define PTHREAD_KEY_2NDLEVEL_SIZE 32
#define PTHREAD_KEY_1STLEVEL_SIZE \
((PTHREAD_KEYS_MAX + PTHREAD_KEY_2NDLEVEL_SIZE - 1)
/ PTHREAD_KEY_2NDLEVEL_SIZE)
其中在/usr/include/bits/local_lim.h中定义了PTHREAD_KEYS_MAX为1024,所以一维数组大小为32。而具体存放的位置由key值通过如下计算获得:
idx1st = key / PTHREAD_KEY_2NDLEVEL_SIZE
idx2nd = key % PTHREAD_KEY_2NDLEVEL_SIZE
也就是说,数据存放与一个32×32的稀疏矩阵中。一样,访问的时候也由key值通过相似计算获得数据所在位置索引,再取出其中内容返回。
四. 使用范例
如下这个例子没有什么实际意义,只是说明如何使用,以及可以使用这一机制达到存储线程私有数据的目的。
#include
#include
pthread_key_t key;
void echomsg(int t)
{
printf("destructor excuted in thread %d,param=%d\n",pthread_self(),t);
}
void * child1(void *arg)
{
int tid=pthread_self();
printf("thread %d enter\n",tid);
pthread_setspecific(key,(void *)tid);
sleep(2);
printf("thread %d returns %d\n",tid,pthread_getspecific(key));
sleep(5);
}
void * child2(void *arg)
{
int tid=pthread_self();
printf("thread %d enter\n",tid);
pthread_setspecific(key,(void *)tid);
sleep(1);
printf("thread %d returns %d\n",tid,pthread_getspecific(key));
sleep(5);
}
int main(void)
{
int tid1,tid2;
printf("hello\n");
pthread_key_create(&key,echomsg);
pthread_create(&tid1,NULL,child1,NULL);
pthread_create(&tid2,NULL,child2,NULL);
sleep(10);
pthread_key_delete(key);
printf("main thread exit\n");
return 0;
}
给例程建立两个线程分别设置同一个线程私有数据为本身的线程ID,为了检验其私有性,程序错开了两个线程私有数据的写入和读出的时间,从程序运行结果能够看
出,两个线程对TSD的修改互不干扰。同时,当线程退出时,清理函数会自动执行,参数为tid。
Posix线程编程指南(3)
内容:
一. 互斥锁
二. 条件变量
三. 信号灯
四. 异步信号
五. 其余同步方式
相关内容:
(1) 线程建立与取消
(2) 线程私有数据
线程同步
这是一个关于Posix线程编程的专栏。做者在阐明概念的基础上,将向您详细讲述Posix线程库API。本文是第三篇将向您讲述线程同步。
一. 互斥锁
尽管在Posix Thread中一样可使用IPC的信号量机制来实现互斥锁mutex功能,但显然semphore的功能过于强大了,在Posix
Thread中定义了另一套专门用于线程同步的mutex函数。
1. 建立和销毁
有两种方法建立互斥锁,静态方式和动态方式。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_des
troy()除了检查锁状态之外(锁定状态则返回EBUSY)没有其余动做。
2. 互斥锁属性
互斥锁的属性在建立锁的时候指定,在LinuxThreads实现中仅有一个锁类型属性,不一样的锁类型在试图对一个已经被锁定的互斥锁加锁时表现不一样。当前(glibc2.2
.3,linuxthreads0.9)有四个值可供选择:
PTHREAD_MUTEX_TIMED_NP,这是缺省值,也就是普通锁。当一个线程加锁之后,其他请求锁的线程将造成一个等待队列,并在解锁后按优先级得到锁。这种锁策略保
证了资源分配的公平性。
PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,容许同一个线程对同一个锁成功得到屡次,并经过屡次unlock解锁。若是是不一样线程请求,则在加锁线程解锁时从新竞争。
PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,若是同一个线程请求同一个锁,则返回EDEADLK,不然与PTHREAD_MUTEX_TIMED_NP类型动做相同。这样就保证当不容许屡次
加锁时不会出现最简单状况下的死锁。
PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,动做最简单的锁类型,仅等待解锁后从新竞争。
3. 锁操做
锁操做主要包括加锁pthread_mutex_lock()、解锁pthread_mutex_unlock()和测试加锁pthread_mutex_trylock()三个,不论哪一种类型的锁,都不可能被两个不一样的
线程同时获得,而必须等待解锁。对于普通锁和适应锁类型,解锁者能够是同进程内任何线程;而检错锁则必须由加锁者解锁才有效,不然返回EPERM;对于嵌套锁
,文档和实现要求必须由加锁者解锁,但实验结果代表并无这种限制,这个不一样目前尚未获得解释。在同一进程中的线程,若是加锁后没有解锁,则任何其余线
程都没法再得到锁。
int pthread_mutex_lock(pthread_mutex_t *mutex)
int pthread_mutex_unlock(pthread_mutex_t *mutex)
int pthread_mutex_trylock(pthread_mutex_t *mutex)
pthread_mutex_trylock()语义与pthread_mutex_lock()相似,不一样的是在锁已经被占据时返回EBUSY而不是挂起等待。
4. 其余
POSIX线程锁机制的Linux实现都不是取消点,所以,延迟取消类型的线程不会因收到取消信号而离开加锁等待。值得注意的是,若是线程在加锁后解锁前被取消,锁
将永远保持锁定状态,所以若是在关键区段内有取消点存在,或者设置了异步取消类型,则必须在退出回调函数中解锁。
这个锁机制同时也不是异步信号安全的,也就是说,不该该在信号处理过程当中使用互斥锁,不然容易形成死锁。
二. 条件变量
条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动做:一个线程等待"条件变量的条件成立"而挂起;另外一个线程使"条件成立"(给出条件
成立信号)。为了防止竞争,条件变量的使用老是和一个互斥锁结合在一块儿。
1. 建立和注销
条件变量和互斥锁同样,都有静态动态两种建立方式,静态方式使用PTHREAD_COND_INITIALIZER常量,以下:
pthread_cond_t cond=PTHREAD_COND_INITIALIZER
动态方式调用pthread_cond_init()函数,API定义以下:
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr)
尽管POSIX标准中为条件变量定义了属性,但在LinuxThreads中没有实现,所以cond_attr值一般为NULL,且被忽略。
注销一个条件变量须要调用pthread_cond_destroy(),只有在没有线程在该条件变量上等待的时候才能注销这个条件变量,不然返回EBUSY。由于Linux实现的条件变
量没有分配什么资源,因此注销动做只包括检查是否有等待线程。API定义以下:
int pthread_cond_destroy(pthread_cond_t *cond)
2. 等待和激发
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime)
等待条件有两种方式:无条件等待pthread_cond_wait()和计时等待pthread_cond_timedwait(),其中计时等待方式若是在给定时刻前条件没有知足,则返回ETIMEOU
T,结束等待,其中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()前的加锁动做对应。
激发条件有两种形式,pthread_cond_signal()激活一个等待该条件的线程,存在多个等待线程时按入队顺序激活其中一个;而pthread_cond_broadcast()则激活所
有等待线程。
3. 其余
pthread_cond_wait()和pthread_cond_timedwait()都被实现为取消点,所以,在该处等待的线程将当即从新运行,在从新锁定mutex后离开pthread_cond_wait(),
而后执行取消动做。也就是说若是pthread_cond_wait()被取消,mutex是保持锁定状态的,于是须要定义退出回调函数来为其解锁。
如下示例集中演示了互斥锁和条件变量的结合使用,以及取消对于条件等待动做的影响。在例子中,有两个线程被启动,并等待同一个条件变量,若是不使用退出回
调函数(见范例中的注释部分),则tid2将在pthread_mutex_lock()处永久等待。若是使用回调函数,则tid2的条件等待及主线程的条件激发都能正常工做。
#include
#include
#include
pthread_mutex_t mutex;
pthread_cond_t cond;
void * child1(void *arg)
{
pthread_cleanup_push(pthread_mutex_unlock,&mutex); /* comment 1 */
while(1){
printf("thread 1 get running \n");
printf("thread 1 pthread_mutex_lock returns %d\n",
pthread_mutex_lock(&mutex));
pthread_cond_wait(&cond,&mutex);
printf("thread 1 condition applied\n");
pthread_mutex_unlock(&mutex);
sleep(5);
}
pthread_cleanup_pop(0); /* comment 2 */
}
void *child2(void *arg)
{
while(1){
sleep(3); /* comment 3 */
printf("thread 2 get running.\n");
printf("thread 2 pthread_mutex_lock returns %d\n",
pthread_mutex_lock(&mutex));
pthread_cond_wait(&cond,&mutex);
printf("thread 2 condition applied\n");
pthread_mutex_unlock(&mutex);
sleep(1);
}
}
int main(void)
{
int tid1,tid2;
printf("hello, condition variable test\n");
pthread_mutex_init(&mutex,NULL);
pthread_cond_init(&cond,NULL);
pthread_create(&tid1,NULL,child1,NULL);
pthread_create(&tid2,NULL,child2,NULL);
do{
sleep(2); /* comment 4 */
pthread_cancel(tid1); /* comment 5 */
sleep(2); /* comment 6 */
pthread_cond_signal(&cond);
}while(1);
sleep(100);
pthread_exit(0);
}
若是不作注释5的pthread_cancel()动做,即便没有那些sleep()延时操做,child1和child2都能正常工做。注释3和注释4的延迟使得child1有时间完成取消动做,从
而使child2能在child1退出以后进入请求锁操做。若是没有注释1和注释2的回调函数定义,系统将挂起在child2请求锁的地方;而若是同时也不作注释3和注释4的延
时,child2能在child1完成取消动做之前获得控制,从而顺利执行申请锁的操做,但却可能挂起在pthread_cond_wait()中,由于其中也有申请mutex的操做。child1
函数给出的是标准的条件变量的使用方式:回调函数保护,等待条件前锁定,pthread_cond_wait()返回后解锁。
条件变量机制不是异步信号安全的,也就是说,在信号处理函数中调用pthread_cond_signal()或者pthread_cond_broadcast()极可能引发死锁。
三. 信号灯
信号灯与互斥锁和条件变量的主要不一样在于"灯"的概念,灯亮则意味着资源可用,灯灭则意味着不可用。若是说后两中同步方式侧重于"等待"操做,即资源不可用的
话,信号灯机制则侧重于点灯,即告知资源可用;没有等待线程的解锁或激发条件都是没有意义的,而没有等待灯亮的线程的点灯操做则有效,且能保持灯亮状态。
固然,这样的操做原语也意味着更多的开销。
信号灯的应用除了灯亮/灯灭这种二元灯之外,也能够采用大于1的灯数,以表示资源数大于1,这时能够称之为多元灯。
1. 建立和注销
POSIX信号灯标准定义了有名信号灯和无名信号灯两种,但LinuxThreads的实现仅有无名灯,同时有名灯除了老是可用于多进程之间之外,在使用上与无名灯并无
很大的区别,所以下面仅就无名灯进行讨论。
int sem_init(sem_t *sem, int pshared, unsigned int value)
这是建立信号灯的API,其中value为信号灯的初值,pshared表示是否为多进程共享而不只仅是用于一个进程。LinuxThreads没有实现多进程共享信号灯,所以全部
非0值的pshared输入都将使sem_init()返回-1,且置errno为ENOSYS。初始化好的信号灯由sem变量表征,用于如下点灯、灭灯操做。
int sem_destroy(sem_t * sem)
被注销的信号灯sem要求已没有线程在等待该信号灯,不然返回-1,且置errno为EBUSY。除此以外,LinuxThreads的信号灯注销函数不作其余动做。
2. 点灯和灭灯
int sem_post(sem_t * sem)
点灯操做将信号灯值原子地加1,表示增长一个可访问的资源。
int sem_wait(sem_t * sem)
int sem_trywait(sem_t * sem)
sem_wait()为等待灯亮操做,等待灯亮(信号灯值大于0),而后将信号灯原子地减1,并返回。sem_trywait()为sem_wait()的非阻塞版,若是信号灯计数大于0,则
原子地减1并返回0,不然当即返回-1,errno置为EAGAIN。
3. 获取灯值
int sem_getvalue(sem_t * sem, int * sval)
读取sem中的灯计数,存于*sval中,并返回0。
4. 其余
sem_wait()被实现为取消点,并且在支持原子"比较且交换"指令的体系结构上,sem_post()是惟一能用于异步信号处理函数的POSIX异步信号安全的API。
四. 异步信号
因为LinuxThreads是在核外使用核内轻量级进程实现的线程,因此基于内核的异步信号操做对于线程也是有效的。但同时,因为异步信号老是实际发往某个进程,所
以没法实现POSIX标准所要求的"信号到达某个进程,而后再由该进程将信号分发到全部没有阻塞该信号的线程中"原语,而是只能影响到其中一个线程。
POSIX异步信号同时也是一个标准C库提供的功能,主要包括信号集管理(sigemptyset()、sigfillset()、sigaddset()、sigdelset()、sigismember()等)、信号处
理函数安装(sigaction())、信号阻塞控制(sigprocmask())、被阻塞信号查询(sigpending())、信号等待(sigsuspend())等,它们与发送信号的kill()等函数
配合就能实现进程间异步信号功能。LinuxThreads围绕线程封装了sigaction()何raise(),本节集中讨论LinuxThreads中扩展的异步信号函数,包括pthread_sigmas
k()、pthread_kill()和sigwait()三个函数。毫无疑问,全部POSIX异步信号函数对于线程都是可用的。
int pthread_sigmask(int how, const sigset_t *newmask, sigset_t *oldmask)
设置线程的信号屏蔽码,语义与sigprocmask()相同,但对不容许屏蔽的Cancel信号和不容许响应的Restart信号进行了保护。被屏蔽的信号保存在信号队列中,可由
sigpending()函数取出。
int pthread_kill(pthread_t thread, int signo)
向thread号线程发送signo信号。实现中在经过thread线程号定位到对应进程号之后使用kill()系统调用完成发送。
int sigwait(const sigset_t *set, int *sig)
挂起线程,等待set中指定的信号之一到达,并将到达的信号存入*sig中。POSIX标准建议在调用sigwait()等待信号之前,进程中全部线程都应屏蔽该信号,以保证
仅有sigwait()的调用者得到该信号,所以,对于须要等待同步的异步信号,老是应该在建立任何线程之前调用pthread_sigmask()屏蔽该信号的处理。并且,调用si
gwait()期间,原来附接在该信号上的信号处理函数不会被调用。
若是在等待期间接收到Cancel信号,则当即退出等待,也就是说sigwait()被实现为取消点。
五. 其余同步方式
除了上述讨论的同步方式之外,其余不少进程间通讯手段对于LinuxThreads也是可用的,好比基于文件系统的IPC(管道、Unix域Socket等)、消息队列(Sys.V或者
Posix的)、System
V的信号灯等。只有一点须要注意,LinuxThreads在核内是做为共享存储区、共享文件系统属性、共享信号处理、共享文件描述符的独立进程看待的。
Posix线程编程指南(4)
内容:
1. 线程终止方式
2. 线程终止时的清理
3. 线程终止的同步及其返回值
4. 关于pthread_exit()和return
参考资料
相关内容:
(1) 线程建立与取消
(2) 线程私有数据
(3) 线程同步
线程终止
这是一个关于Posix线程编程的专栏。做者在阐明概念的基础上,将向您详细讲述Posix线程库API。本文是第四篇将向您讲述线程停止。
1. 线程终止方式
通常来讲,Posix的线程终止有两种状况:正常终止和非正常终止。线程主动调用pthread_exit()或者从线程函数中return都将使线程正常退出,这是可预见的退出
方式;非正常终止是线程在其余线程的干预下,或者因为自身运行出错(好比访问非法地址)而退出,这种退出方式是不可预见的。
2. 线程终止时的清理
不管是可预见的线程终止仍是异常终止,都会存在资源释放的问题,在不考虑因运行出错而退出的前提下,如何保证线程终止时能顺利的释放掉本身所占用的资源,
特别是锁资源,就是一个必须考虑解决的问题。
最常常出现的情形是资源独占锁的使用:线程为了访问临界资源而为其加上锁,但在访问过程当中被外界取消,若是线程处于响应取消状态,且采用异步方式响应,或
者在打开独占锁之前的运行路径上存在取消点,则该临界资源将永远处于锁定状态得不到释放。外界取消操做是不可预见的,所以的确须要一个机制来简化用于资源
释放的编程。
在POSIX线程API中提供了一个pthread_cleanup_push()/pthread_cleanup_pop()函数对用于自动释放资源--从pthread_cleanup_push()的调用点到pthread_cleanup_
pop()之间的程序段中的终止动做(包括调用pthread_exit()和取消点终止)都将执行pthread_cleanup_push()所指定的清理函数。API定义以下:
void pthread_cleanup_push(void (*routine) (void *), void *arg)
void pthread_cleanup_pop(int execute)
pthread_cleanup_push()/pthread_cleanup_pop()采用先入后出的栈结构管理,void routine(void
*arg)函数在调用pthread_cleanup_push()时压入清理函数栈,屡次对pthread_cleanup_push()的调用将在清理函数栈中造成一个函数链,在执行该函数链时按照压
栈的相反顺序弹出。execute参数表示执行到pthread_cleanup_pop()时是否在弹出清理函数的同时执行该函数,为0表示不执行,非0为执行;这个参数并不影响异常
终止时清理函数的执行。
pthread_cleanup_push()/pthread_cleanup_pop()是以宏方式实现的,这是pthread.h中的宏定义:
#define pthread_cleanup_push(routine,arg) \
{ struct _pthread_cleanup_buffer _buffer; \
_pthread_cleanup_push (&_buffer, (routine), (arg));
#define pthread_cleanup_pop(execute) \
_pthread_cleanup_pop (&_buffer, (execute)); }
可见,pthread_cleanup_push()带有一个"{",而pthread_cleanup_pop()带有一个"}",所以这两个函数必须成对出现,且必须位于程序的同一级别的代码段中才能
经过编译。在下面的例子里,当线程在"do some work"中终止时,将主动调用pthread_mutex_unlock(mut),以完成解锁动做。
pthread_cleanup_push(pthread_mutex_unlock, (void *) &mut);
pthread_mutex_lock(&mut);
/* do some work */
pthread_mutex_unlock(&mut);
pthread_cleanup_pop(0);
必需要注意的是,若是线程处于PTHREAD_CANCEL_ASYNCHRONOUS状态,上述代码段就有可能出错,由于CANCEL事件有可能在pthread_cleanup_push()和pthread_mutex
_lock()之间发生,或者在pthread_mutex_unlock()和pthread_cleanup_pop()之间发生,从而致使清理函数unlock一个并无加锁的mutex变量,形成错误。所以,
在使用清理函数的时候,都应该暂时设置成PTHREAD_CANCEL_DEFERRED模式。为此,POSIX的Linux实现中还提供了一对不保证可移植的pthread_cleanup_push_defer_
np()/pthread_cleanup_pop_defer_np()扩展函数,功能与如下代码段至关:
{ int oldtype;
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &oldtype);
pthread_cleanup_push(routine, arg);
...
pthread_cleanup_pop(execute);
pthread_setcanceltype(oldtype, NULL);
}
3. 线程终止的同步及其返回值
通常状况下,进程中各个线程的运行都是相互独立的,线程的终止并不会通知,也不会影响其余线程,终止的线程所占用的资源也并不会随着线程的终止而获得释放
。正如进程之间能够用wait()系统调用来同步终止并释放资源同样,线程之间也有相似机制,那就是pthread_join()函数。
void pthread_exit(void *retval)
int pthread_join(pthread_t th, void **thread_return)
int pthread_detach(pthread_t th)
pthread_join()的调用者将挂起并等待th线程终止,retval是pthread_exit()调用者线程(线程ID为th)的返回值,若是thread_return不为NULL,则*thread_retur
n=retval。须要注意的是一个线程仅容许惟一的一个线程使用pthread_join()等待它的终止,而且被等待的线程应该处于可join状态,即非DETACHED状态。
若是进程中的某个线程执行了pthread_detach(th),则th线程将处于DETACHED状态,这使得th线程在结束运行时自行释放所占用的内存资源,同时也没法由pthread_
join()同步,pthread_detach()执行以后,对th请求pthread_join()将返回错误。
一个可join的线程所占用的内存仅当有线程对其执行了pthread_join()后才会释放,所以为了不内存泄漏,全部线程的终止,要么已设为DETACHED,要么就须要使
用pthread_join()来回收。
4. 关于pthread_exit()和return
理论上说,pthread_exit()和线程宿体函数退出的功能是相同的,函数结束时会在内部自动调用pthread_exit()来清理线程相关的资源。但实际上两者因为编译器的
处理有很大的不一样。
在进程主函数(main())中调用pthread_exit(),只会使主函数所在的线程(能够说是进程的主线程)退出;而若是是return,编译器将使其调用进程退出的代码(
如_exit()),从而致使进程及其全部线程结束运行。
其次,在线程宿主函数中主动调用return,若是return语句包含在pthread_cleanup_push()/pthread_cleanup_pop()对中,则不会引发清理函数的执行,反而会致使
segment fault。
Posix线程编程指南(5)
内容:
1.得到本线程ID
2.判断两个线程是否为同一线程
3.仅执行一次的操做
4.pthread_kill_other_threads_np()
相关内容:
(1) 线程建立与取消
(2) 线程私有数据
(3) 线程同步
(4) 线程终止
这是一个关于Posix线程编程的专栏。做者在阐明概念的基础上,将向您详细讲述Posix线程库API。本文是第五篇将向您讲述pthread_self()、pthread_equal()和pt
hread_once()等杂项函数。
在Posix线程规范中还有几个辅助函数难以归类,暂且称其为杂项函数,主要包括pthread_self()、pthread_equal()和pthread_once()三个,另外还有一个LinuxThr
eads非可移植性扩展函数pthread_kill_other_threads_np()。本文就介绍这几个函数的定义和使用。
1. 得到本线程ID
pthread_t pthread_self(void)
本函数返回本线程的标识符。
在LinuxThreads中,每一个线程都用一个pthread_descr结构来描述,其中包含了线程状态、线程ID等全部须要的数据结构,此函数的实现就是在线程栈帧中找到本线
程的pthread_descr结构,而后返回其中的p_tid项。
pthread_t类型在LinuxThreads中定义为无符号长整型。
2. 判断两个线程是否为同一线程
int pthread_equal(pthread_t thread1, pthread_t thread2)
判断两个线程描述符是否指向同一线程。在LinuxThreads中,线程ID相同的线程必然是同一个线程,所以,这个函数的实现仅仅判断thread1和thread2是否相等。
3. 仅执行一次的操做
int pthread_once(pthread_once_t *once_control, void (*init_routine) (void))
本函数使用初值为PTHREAD_ONCE_INIT的once_control变量保证init_routine()函数在本进程执行序列中仅执行一次。
#include
#include
pthread_once_t once=PTHREAD_ONCE_INIT;
void once_run(void)
{
printf("once_run in thread %d\n",pthread_self());
}
void * child1(void *arg)
{
int tid=pthread_self();
printf("thread %d enter\n",tid);
pthread_once(&once,once_run);
printf("thread %d returns\n",tid);
}
void * child2(void *arg)
{
int tid=pthread_self();
printf("thread %d enter\n",tid);
pthread_once(&once,once_run);
printf("thread %d returns\n",tid);
}
int main(void)
{
int tid1,tid2;
printf("hello\n");
pthread_create(&tid1,NULL,child1,NULL);
pthread_create(&tid2,NULL,child2,NULL);
sleep(10);
printf("main thread exit\n");
return 0;
}
once_run()函数仅执行一次,且究竟在哪一个线程中执行是不定的,尽管pthread_once(&once,once_run)出如今两个线程中。
LinuxThreads使用互斥锁和条件变量保证由pthread_once()指定的函数执行且仅执行一次,而once_control则表征是否执行过。若是once_control的初值不是PTHREA
D_ONCE_INIT(LinuxThreads定义为0),pthread_once()的行为就会不正常。在LinuxThreads中,实际"一次性函数"的执行状态有三种:NEVER(0)、IN_PROGRESS
(1)、DONE(2),若是once初值设为1,则因为全部pthread_once()都必须等待其中一个激发"已执行一次"信号,所以全部pthread_once()都会陷入永久的等待中
;若是设为2,则表示该函数已执行过一次,从而全部pthread_once()都会当即返回0。
4. pthread_kill_other_threads_np()
void pthread_kill_other_threads_np(void)
这个函数是LinuxThreads针对自己没法实现的POSIX约定而作的扩展。POSIX要求当进程的某一个线程执行exec*系统调用在进程空间中加载另外一个程序时,当前进程
的全部线程都应终止。因为LinuxThreads的局限性,该机制没法在exec中实现,所以要求线程执行exec前手工终止其余全部线程。pthread_kill_other_threads_np(
)的做用就是这个。
须要注意的是,pthread_kill_other_threads_np()并无经过pthread_cancel()来终止线程,而是直接向管理线程发"进程退出"信号,使全部其余线程都结束运行
,而不通过Cancel动做,固然也不会执行退出回调函数。尽管LinuxThreads的实验结果与文档说明相同,但代码实现中倒是用的__pthread_sig_cancel信号来kill线
程,应该效果与执行pthread_cancel()是同样的,其中缘由目前还不清楚。