可重入函数
数组
中断一个可重入函数的执行,转而执行另一个函数(通常为信号处理程序,注意此时依然为同一个线程
),
返回可重入函数执行不会出现错误。安全
可重入与异步信号安全等价(APUE 3 edition, 10.6 )
多线程
可重入函数除了使用本身栈上的变量之外不依赖于任何环境(包括 static),这样的函数就是可重入的,
能够容许有该函数的多个副本同时运行异步
线程安全:
函数
若是一个函数在相同的时间点能够被多个线程
安全地调用,就称该函数是线程安全的。spa
1) 若一个函数如同可重入函数同样,也没有使用静态数据(全局变量或static局部变量),那么该函数也是线程安全的。
2) 或使用了静态数据,但采用了同步机制,保证线程的安全调用,那么也是线程安全的。操作系统
可重入与线程安全的关系:
线程
-------------------------------[Figure 1]--------------------------------
指针
举例说明:func是线程安全的,但不是可重入的:
1)线程安全是显然的,第b步在任意时刻只有一个线程访问。
2)假设进程执行完第a步,接收到一个信号,转而执行信号处理程序,恰巧该信号处理程序中也调用了函数func,那么就陷入了死锁。
3) 固然能够利用递归锁,那么func也是可重入的,可是递归锁的使用可能会带来其余严重问题
rest
-------------------------------[Figure 2]--------------------------------
支持线程安全函数的操做系统实现会在<unistd.h>中定义符号_POSIX_THREAD_SAFE_FUNCTIONS,对POSIX.1中的一些非线程安全函数,
它会提供可替代的线程安全版本。下面列出这些函数的线程安全版本。
POSIX.1还提供了以线程安全的方式管理FILE对象的方法。可使用flockfile和ftrylockfile获取给定FILE对象关联的锁。
这个锁是递归的:当你占有这把锁的时候,仍是能够再次获取该锁,并且不会致使死锁。
int ftrylockfile(FILE *fp); void flockfile(FILE *fp); void funlockfile(FILE *fp);
使用线程特定数据理由:
须要基于线程来维护一些数据(好比,线程id)
但愿可以提供一种机制能够在多线程的环境下使用基于进程的接口。(好比,errno)
int pthread_key_create(pthread_key_t *keyp, void (*destructor)(void *)); void* pthread_getspecific(pthread_key_t key); int pthread_setspecific(pthread_key_t key, const void *value);
int pthread_key_delete(pthread_key_t *key);
注意:取消键与当前线程TSD的关联关系(键与其余线程TSD的关联关系不变),但并不会激活与键关联的析构函数,
须要应用程序本身利用free函数
使用TSD过程:
1. 启动一个进程并建立了若干线程(A,B),其中一个线程(好比线程A),要申请线程私有数据,系统调用
pthread_key_creat在下图所示的key结构数组中找到第一个未用的元素,并把它的键(0)返回给调用者
2. 线程a经过pthrea_getspecific调用得到线程a的pkey[0]值,返回的是一个空指针NULL,
3. 用malloc在堆里分配一段空间,使用pthread_setspecific调用将pkey[0]指向刚才分配的内存区域。
4. 若线程b若使用相同的键,线程B只须要重复第三步。最终结果以下Figure 3所示。
------------------------------------------[Figure 3]----------------------------------------
须要确保分配的键并非因为在初始化阶段的竞争而发生变化:
int pthread_once(pthread_once_t *initflag, void (*initfn)(void));
struct tsd { pthread_t tid; }; pthread_key_t key; pthread_once_t create_done = PTHREAD_ONCE_INIT; void destructor(void* arg){ struct tsd* tsdptr = (struct tsd*)arg; free(tsdptr); } void key_create(void){ pthread_key_create(&key, destructor); } void key_init(){ struct tsd *tsdptr = (struct tsd*)malloc(sizeof(struct tsd)); tsdptr->tid = pthread_self(); pthread_setspecific(key, tsdptr); } void* p_fun(void* arg){ pthread_once(&create_done, key_create); key_init(); return (void*)0; }
1. 每一个线程都有本身的信号屏蔽字,可是信号的处理是进程中全部线程共享的.
2. 进程中的信号是传递到单个线程的,线程中必须使用pthread_sigmask设置信号屏蔽字.
3. 若是一个信号与硬件故障相关,该信号通常被发送到引发该事件的线程中,不然信号被发送到任意一个线程
int pthread_sigmask(int how, const sigset_t *set, sigset_t *oldset); int pthread_kill(pthread_t thread, int sig);
[pthread_xxx的函数通常返回错误码,而不是设置errno]
注意,用sigprocmask修改的是进程(主线程)的信号屏障字,而不是本线程的信号屏蔽字
int sigwait(const sigset_t * restrict set, int * restrict signop); signop为当前接受到的信号[APUE 3 edition 中文版的解释错误]
1. set为等待的信号或信号集
2. 若信号集中的某些信号在sigwait调用时处于挂起状态,那么sigwait将无阻塞地返回,且移除这些挂起信号,若为排队信号,一次移除一个
3. 线程在调用sigwait以前,必须阻塞等待的信号(避免出现窗口期)。sigwait函数会原子地取消信号集的阻塞状态,直到信号到来
4. 任意信号均可以唤醒调用sigwait的线程(和进程的sigsuspend很不一样)
5. 使用sigwait的好处在于它能够简化信号处理,利用一个或多个线程专门处理信号,从而将异步产生的信号以同步方式处理
---5.1 假设专用线程A处理SIGINT,SIGQUIT信号, 在建立线程A以前,主线程首先屏蔽这两个信号
---5.2 线程A建立以后,继承主线程的信号屏蔽字,线程A调用sigwait函数
---5.3 sigwait取消对SIGINT与SIGQUIT的屏蔽,接着线程A阻塞,等待任意信号的到来
---5.4 当一个信号到来时,唤醒线程A,在线程A的正常的上下文中调用信号的处理程序
6. 若多个线程在sigwait的调用中由于同一个信号而阻塞,那么在信号传递的时候,就只有一个线程从中返回
7. 若一个信号被捕获(使用sigaction创建了一个信号处理程序),而一个线程正在sigwait调用中等待同一个信号,那么不一样操做系统的处理方式不一样,因此尽可能避免
void* p_fun(void* arg){ sigset_t sigs; sigemptyset(&sigs); sigaddset(&sigs, (int)arg); int sig; for(;;){ if (sigwait(&sigs, &sig) != 0){ fprintf(stderr, "sigwait err"); return (void*)-1; } if (sig == SIGINT){ printf("tid : %ld receive SIGINT.\n", pthread_self()); } if (sig == SIGTSTP){ printf("tid : %ld receive SIGTSTP.\n", pthread_self()); } } return (void*)0; }
int main(){ sigset_t sigs; sigemptyset(&sigs); sigaddset(&sigs, SIGINT); sigaddset(&sigs, SIGTSTP); sigprocmask(SIG_SETMASK, &sigs, NULL); pthread_t tid[2]; if (pthread_create(&tid[0], NULL, p_fun, (void*)SIGINT) != 0){ fprintf(stderr, "pthread_create err"); exit(-1); } if (pthread_create(&tid[1], NULL, p_fun, (void*)SIGTSTP) != 0){ fprintf(stderr, "pthread_create err"); exit(-1); } for(;;){ /*main proccess*/ } exit(0); }
int pthread_kill(pthread_t tid, int signo);
能够传一个0值来检查线程tid是否存在,相似kill检查进程的存在性
闹钟定时器是进程资源,全部线程共享相同的闹钟
在子进程内部,只存在一个线程,以下图所示,子进程中只有线程T3,由于在父进程中由线程T3建立了子进程。
因此互斥量M1,M2在子进程中就没法解锁。
``
不一致解决方法:
1. 若是子进程从fork返回之后立刻调用其中一个exec函数,能够避免这种不一致,在fork返回和子进程调用其中一个exec函数之间,
子进程只能调用异步信号安全的函数
2. 调用pthread_atfork函数创建fork处理程序
int pthread_atfork()(void (*prepare)(void),void (*parent)(void),void (*child)(void));
------2.1 prepare由父进程在fork建立子进程前调用,prepare的任务是获取父进程定义的全部锁
------2.2 parent是在fork建立了子进程之后,在fork返回以前在父进程环境中调用的,parent的任务是对prepare得到的锁进行解锁
------2.3 child在fork返回以前在子进程环境中调用,与parent同样,child必须释放prepare处理程序得到的全部锁
------2.4 能够屡次调用pthread_atfork从而设置多套fork处理程序。parent和child是以他们注册时的顺序进行调用,
----------prepare的调用顺序与他们注册时的顺序相反,好比若模块A调用模块B,每一个模块都有本身的一套锁。
----------那么模块B必须在模块A以前设置它的pthread_atfork函数