《APUE》笔记-第十二章-线程控制
2016-11-12 13:28
302 查看
重点:线程属性、互斥量属性、线程私有数据、线程和fork、线程和信号
管理这些属性的函数都遵循相同的模式
(1)每个对象与它自己类型的属性对象进行关联(线程与线程属性关联,互斥量与互斥量属性关联,等等)
(2)有一个初始化函数,把属性值设为默认值
(3)有一个销毁属性对象的函数(反初始化)
(4)每个属性都有一个获取属性值的函数
(5)每个属性都有一个设置属性值的函数
detachstate---线程的分离状态属性
stackaddr---线程栈的最低地址
stacksize---线程栈的最小长度(字节数)
guardsize---线程栈末尾的警戒缓冲区大小
pthread_attr_t:线程属性变量类型
int pthread_attr_init(pthread_attr_t *attr);//初始化
int pthread_attr_destroy(pthread_attr_t *attr);//反初始化
int pthread_attr_getdetachstate(const pthread_attr_t *restrict attr, int *detachstate);//获取分离状态
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);//设置分离状态
detachstate只能为:PTHREAD_CREATE_DETACHED,以分离状态启动线程;或者PTHREAD_CREATE_JOINABLE,正常启动线程
如果在创建线程时就知道不需要了解线程的终止状态,可以设置让线程属于分离状态;处于分离状态的线程,退出时,可回收其资源
int pthread_attr_getstack(const pthread_attr_t *restrict attr, void **restrict stackaddr, size_t *restrict stacksize);//获取线程栈最低地址
int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize);//设置线程栈最低地址
int pthread_attr_getstacksize(const pthread_attr_t *restrict attr, size_t *restrict stacksize);//获取线程栈最小长度
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);//设置线程栈最小长度
int pthread_attr_getguardsize(const pthread_attr_t *restrict attr, size_t *restrict guardsize);//获取警戒缓冲区大小
int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);//设置警戒缓冲区大小
所有函数:成功,返回0;出错,返回错误编号
程序练习:设置线程分离状态,并获取线程各项属性。
分析:
在线程的起始地址函数里,通过pthread_getattr_np来获取指定线程ID的线程属性
int pthread_mutexattr_init(pthread_mutexattr_t *attr);//初始化
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);//反初始化
在进程中,多个线程访问同一个同步对象,此时互斥量虚设置为PTHREAD_PROCESS_PRIVATE
存在这样的机制:允许相互独立的多个进程把同一个内存数据块映射到它们各自独立的地址空间中,此时也需要进行同步。如果进程共享互斥量属性设置为PTHREAD_PROCESS_SHARED,则从多个进程彼此之间共享的内存数据块中分配的互斥量就可以用于这些进程的同步。(意味着互斥量可用于进程间同步)
总结:同一进程内的多线程间互斥量同步用PTHREAD_PROCESS_PRIVATE,多进程间互斥量同步用PTHREAD_PROCESS_SHARED
int pthread_mutexattr_getpshared(const pthread_mutexattr_t *restrict attr, int *restrict pshared);//获取进程共享属性
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared);//设置进程共享属性
PTHREAD_MUTEX_STALLED:意味着持有互斥量的进程终止时不需要采取特别的动作,结果一般是未定义的(意思就是不会对互斥量解锁)
PTHREAD_MUTEX_ROBUST:对互斥量进行恢复
int pthread_mutexattr_getrobust(const pthread_mutexattr_t *restrict attr, int *restrict robust);//获取
int pthread_mutexattr_setrobust(pthread_mutexattr_t *attr, int robust);//设置
int pthread_mutexattr_gettype(const pthread_mutexattr_t *restrict attr, int *restrict type);//获取
int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type);//设置
程序练习:互斥量1类型属性设置为标准类型(多次加锁会造成死锁),互斥量2类型属性设置为允许多次加锁,创建两个线程,分别对两个互斥量加两次锁,输出结果。
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#define ERR_EXIT(m)\
{\
perror(m);\
exit(EXIT_FAILURE);\
}
int g_first = 3;
int g_second = 9;
pthread_mutex_t mutex1;//互斥量
pthread_mutex_t mutex2;
void *thread_func1(void *arg)
{
printf("in thread1, now, lock mutex1\n");
pthread_mutex_lock(&mutex1);//锁住互斥量
printf("now, lock mutex1 again\n");
pthread_mutex_lock(&mutex1);//再次锁住互斥量
printf("g_first = %d\n", g_first);
pthread_mutex_unlock(&mutex1);
}
void *thread_func2(void *arg)
{
printf("in thread2, now, lock mutex2\n");
pthread_mutex_lock(&mutex2);//锁住互斥量
printf("now, lock mutex2 again\n");
pthread_mutex_lock(&mutex2);//再次锁住互斥量
printf("g_second = %d\n", g_second);
pthread_mutex_unlock(&mutex2);
}
int main()
{
pthread_mutexattr_t attr1;//互斥量属性
pthread_mutexattr_t attr2;
if (pthread_mutexattr_init(&attr1) != 0)//初始化互斥量属性
ERR_EXIT("pthread_mutexattr_init() error");
if (pthread_mutexattr_init(&attr2) != 0)
ERR_EXIT("pthread_mutexattr_init() error");
if (pthread_mutexattr_settype(&attr1, PTHREAD_MUTEX_NORMAL) != 0)//默认互斥量类型
ERR_EXIT("pthread_mutexattr_settype() error");
if (pthread_mutexattr_settype(&attr2, PTHREAD_MUTEX_RECURSIVE) != 0)//允许多次加锁
ERR_EXIT("pthread_mutexattr_settype() error");
if (pthread_mutex_init(&mutex1, &attr1) != 0)//用互斥量属性来初始化互斥量
ERR_EXIT("pthread_mutex_init() error");
if (pthread_mutex_init(&mutex2, &attr2) != 0)
ERR_EXIT("pthread_mutex_init() error");
pthread_t tid1, tid2;
pthread_attr_t thread_attr;//线程属性
if (pthread_attr_init(&thread_attr) != 0)//初始化线程属性
ERR_EXIT("pthread_attr_init() error");
if (pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED) != 0)//设置线程分离状态
ERR_EXIT("pthread_attr_setdetachstate() error");
if (pthread_create(&tid1, &thread_attr, thread_func1, NULL) != 0)//创建线程1
ERR_EXIT("pthread_create() error");
if (pthread_create(&tid2, &thread_attr, thread_func2, NULL) != 0)//创建线程2
ERR_EXIT("pthread_create() error");
sleep(5);
exit(0);
}
结果:
分析:
1.因为互斥量mutex1的类型属性为PTHREAD_MUTEX_NORMAL,所以当对mutex1再次加锁时,造成死锁。而对mutex2再次加锁则不会,因为它的类型属性为PTHREAD_MUTEX_RECURSIVE。5秒过后由于main主线程调用exit退出,所以整个进程退出。
2.因为事先知道不需要获取线程1,2的退出码,所以将线程的属性设置为PTHREAD_CREATE_DETACHED
3.设置线程属性和互斥量属性以及后面的读写锁、条件变量等属性时,一定要先进行init初始化,否则程序运行时会发“段错误”
int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);//初始化
int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);//反初始化
int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *restrict attr, int *restrict pshared);//获取
int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared);//设置
时钟属性控制计算pthread_cond_timedwait函数的超时参数(tsptr)时采用的是哪个时钟
int pthread_condattr_init(pthread_condattr_t *attr);
int pthread_condattr_destroy(pthread_condattr_t *attr);
int pthread_condattr_getpshared(const pthread_condattr_t *restrict attr, int *restrict pshared);
int pthread_condattr_setpshared(pthread_condattr_t *attr, int pshared);
int pthread_condattr_getclock(const pthread_condattr_t *restrict attr, clockid_t *restrict clock_id);
int pthread_condattr_setclock(pthread_condattr_t *attr, clockid_t clock_id);
int pthread_barrierattr_init(pthread_barrierattr_t *attr);
int pthread_barrierattr_destroy(pthread_barrierattr_t *attr);
int pthread_barrierattr_getpshared(const pthread_barrierattr_t *restrict attr, int *restrict pshared);
int pthread_barrierattr_setpshared(pthread_barrierattr_t *attr, int pshared);
很多函数并不是线程安全的,因为它们返回的数据存放在静态缓冲区中。通过修改接口,要求调用者自己提供缓冲区,从而每个线程可以使用不同的缓冲区,从而避免其他线程的干扰可以使函数变为线程安全。
线程安全的函数,并不能对信号处理程序来说该函数也是可重入的。如果函数对异步信号处理程序的重入是安全的,就可以说函数是异步信号安全的。
下面的getenv是一个不可重入的函数,因为所有调用getenv的线程返回的字符都存储在同一个静态缓冲区中。
#include <stdio.h>
#include <string.h>
#include <pthread.h>
static char envbuf[1024];
extern char **environ;
char *getenv(const char *s);
void *pthread_func1(void *arg);
void *pthread_func2(void *arg);
int main()
{
pthread_t tid1, tid2;
void *tret;
pthread_create(&tid1, NULL, pthread_func1, NULL);
pthread_create(&tid2, NULL, pthread_func2, NULL);
pthread_join(tid1, &tret);
printf("thread 1 exit code: %ld\n", (long)tret);
pthread_join(tid2, &tret);
printf("thread 2 exit code: %ld\n", (long)tret);
exit(0);
}
char *getenv(const char *s)
{
int len = strlen(s);
int i;
for (i = 0; environ[i] != NULL; i++)
{
if ((strncmp(s, environ[i], len) == 0) && (environ[i][len] == '='))
{
strcpy(envbuf, &environ[i][len+1]);
return envbuf;
}
}
}
void *pthread_func1(void *arg)
{
printf("thread 1 start\n");
printf("HOME=%s\n", getenv("HOME"));
printf("thread 1 exit\n");
pthread_exit((void *)1);
}
void *pthread_func2(void *arg)
{
printf("thread 2 start\n");
printf("SHELL=%s\n", getenv("SHELL"));
printf("thread 2 exit\n");
pthread_exit((void *)2);
}
使用flockfile和ftrylockfile获取给定FILE对象关联的锁,这个锁是递归的。
int ftrylockfile(FILE *fp);
Returns: 0 if OK, nonzero if lock can’t be acquired
void flockfile(FILE *fp);
void funlockfile(FILE *fp);
为了避免标准I/O在一次一个字符操作时候频繁的获取锁开销,出现了不加锁版本的基于字符的标准I/O例程。
int getchar_unlocked(void);
int getc_unlocked(FILE *fp);
成功:返回下一个字符;出错或到文件尾,返回EOF
int putchar_unlocked(int c);
int putc_unlocked(int c, FILE *fp);
成功,返回c;出错,返回EOF
pthread_key_t:键类型
pthread_once_t:pthread_once()参数,不能是局部变量(如:全局变量或静态变量),只能被初始化为PTHREAD_ONCE_INIT
pthread_once_t initflag = PTHREAD_ONCE_INIT;
int pthread_key_create(pthread_key_t *keyp, void (*destructor)(void *));//创建一个键,并设置为该键关联一个析构函数
int pthread_once(pthread_once_t *initflag, void (*initfn)(void));//确保只调用一次initfn函数,initfn函数将pthread_key_create函数封装,从而确保只调用一次....create函数
成功,返回0;出错,错误编号
void *pthread_getspecific(pthread_key_t key);
返回值:线程特定数据;若没有值与该键关联,则返回NULL
int pthread_setspecific(pthread_key_t key, const void *value);//把键和特定数据相关联
int pthread_key_delete(pthread_key_t key); //取消线程与特定数据之间的联系
成功,返回0;出错,错误编号
使用这些函数的一般步骤是:
1.封装pthread_key_create、设置键和析构函数
2.调用pthread_once函数(确保只产生一次键)
3.调用pthread_getspecific()函数(查看返回值)
4.分配内存(假如没有特定数据与键关联)
5.调用pthread_setspecific()函数(将键与特定数据相关联)
6.设置特定数据
程序练习:创建一个线程,为其设置私有数据。
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#define ERR_EXIT(m)\
{\
perror(m);\
exit(EXIT_FAILURE);\
}
pthread_once_t once = PTHREAD_ONCE_INIT;//pthread_once参数用
pthread_key_t key;//键
//线程析构函数
void destructor(void *arg)
{
printf("this is destructor for thread\n");
}
//线程只被调用一次的函数
void func(void)
{
if (pthread_key_create(&key, destructor) != 0)//创建一个键
ERR_EXIT("pthread_key_create() error");
}
void *thread_func(void *arg)
{
if (pthread_once(&once, func) != 0)//1.确保pthread_key_create()只被调用一次
ERR_EXIT("pthread_once() error");
int *pval;
pval = (int *)pthread_getspecific(key);//2.查看与该键关联的数据,若无,则返回NULL
if (pval == NULL)
pval = (int *)malloc(sizeof(int));//3.为与该键关联的数据分配内存
if (pthread_setspecific(key, pval) != 0)//4.将键与特定数据相关联
ERR_EXIT("pthread_setspecific() error");
*pval = 1;//5.添加数据
int value = *(int *)pthread_getspecific(key);//6.查看与键关联的数据
printf("thread specific data = %d\n", value);
//pthread_key_delete(key);//解除键与数据之间的关系,则不激活析构函数
//value = *(int *)pthread_getspecific(key);
//printf("after delete, thread specific data = %d\n", value);//会发生段错误
pthread_exit((void *)0);
}
int main()
{
pthread_t tid;
if (pthread_create(&tid, NULL, thread_func, NULL) != 0)
ERR_EXIT("pthread_create() error");
if (pthread_join(tid, NULL) != 0)
ERR_EXIT("pthread_join() error");
exit(0);
}
结果:
分析:
1.析构函数的参数是 void *;封装pthread_key_create函数的函数的参数是void。注意区分
2.使用这些函数的一般步骤是:封装pthread_key_create、设置键和析构函数-->调用pthread_once函数(确保只产生一次键)-->调用pthread_getspecific()函数(查看返回值)--->分配内存(假如没有特定数据与键关联)--->调用pthread_setspecific()函数(将键与特定数据相关联)--->设置特定数据
3.pthread_getspecific函数的返回值是void *,所以需要进行类型转换
4.pthread_key_delete解除键与数据关系之后,线程退出时则不会激活析构函数,再取数据时,因为是空指针,所以打印值会出现段错误
程序练习:创建两个线程1,2。线程2读线程1的私有数据,然后线程2在相同的键上设置自己的私有数据:
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#define ERR_EXIT(m)\
{\
perror(m);\
exit(EXIT_FAILURE);\
}
pthread_once_t once = PTHREAD_ONCE_INIT;//pthread_once参数用
pthread_key_t key;//键
//线程析构函数
void destructor(void *arg)
{
printf("this is destructor\n");
}
//线程只被调用一次的函数
void func(void)
{
if (pthread_key_create(&key, destructor) != 0)//和线程1用相同的键,创建一个键
ERR_EXIT("pthread_key_create() error");
}
void *thread_func1(void *arg)
{
if (pthread_once(&once, func) != 0)//确保pthread_key_create()只被调用一次
ERR_EXIT("pthread_once() error");
int *pval;
pval = (int *)pthread_getspecific(key);//查看与该键关联的数据,若无,则返回NULL
if (pval == NULL)
pval = (int *)malloc(sizeof(int));//为与该键关联的数据分配内存
if (pthread_setspecific(key, pval) != 0)//将键与特定数据相关联
ERR_EXIT("pthread_setspecific() error");
*pval = 1;//添加数据
int value = *(int *)pthread_getspecific(key);//查看与键关联的数据
printf("thread 1 specific data = %d\n", value);
pthread_exit((void *)0);
}
void *thread_func2(void *arg)
{
sleep(1);//让线程1先完成工作
int *pthread1_val;
pthread1_val = (int *)pthread_getspecific(key);//线程2读线程1的私有数据
if (pthread1_val == NULL)
printf("can't read data from thread1's specific data\n");
//线程2在和线程1相同的键上关联上自己的数据
if (pthread_once(&once, func) != 0)//确保pthread_key_create()只被调用一次
ERR_EXIT("pthread_once() error");
int *pval = (int *)pthread_getspecific(key);
if (pval == NULL)
pval = (int *)malloc(sizeof(int));
if (pthread_setspecific(key, pval) != 0)
ERR_EXIT("pthread_setspecific() error");
*pval = 5;
int value = *(int *)pthread_getspecific(key);//查看与键关联的数据
printf("thread 2 specific data = %d\n", value);
pthread_exit((void *)0);
}
int main()
{
pthread_t tid1, tid2;
if (pthread_create(&tid1, NULL, thread_func1, NULL) != 0)
ERR_EXIT("pthread_create() error");
if (pthread_create(&tid2, NULL, thread_func2, NULL) != 0)
ERR_EXIT("pthread_create() error");
if (pthread_join(tid1, NULL) != 0)
ERR_EXIT("pthread_join() error");
if (pthread_join(tid2, NULL) != 0)
ERR_EXIT("pthread_join() error");
exit(0);
}
结果:
分析:
1.线程2无法读取线程1的私有数据
2.两个线程调用的析构函数是相同的(即使分别设置各自的析构函数,已测试)
3.数据地址是非空值,析构函数就会被调用,所以free()分配的数据内存后,则不会调用析构函数(已测试)
sigprocmask的行为在多线程中并没有定义,线程必须使用pthread_sigmask
#include <signal.h>
int pthread_sigmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);//和sigprocmask参数意义一样
成功:0;错误:错误编号
线程可以通过调用sigwait等待一个或多个信号的出现
int sigwait(const sigset_t *restrict set, int *restrict signop);
成功:0;错误:错误编号
如果信号集中的某个信号在sigwait调用时处于挂起状态,那么sigwait将无阻塞的返回。在返回之前,sigwait将从进程中移除那些处于挂起等待状态的信号。
要把信号发送给线程,可以调用pthread_kill
int pthread_kill(pthread_t thread, int signo);
成功:0;错误:错误编号
int pthread_atfork(void (*prepare)(void), void (*parent)(void),void (*child)(void));
prepare处理程序由父进程在fork创建子进程前调用,获取父进程定义的所有锁。parent处理程序在fork创建子进程以后,但在fork返回之前在父进程环境调用,对prepare处理程序获得的所有锁进行解锁,child处理程序在fork返回之前在子进程环境中调用,也必须释放prepare处理程序获得的所有锁。parent和child处理程序与它们注册时顺序相同,prepare处理程序调用则与注册时的顺序相反。
写个程序演示如何使用pthread_atfork和fork处理程序。程序如下:
管理这些属性的函数都遵循相同的模式
(1)每个对象与它自己类型的属性对象进行关联(线程与线程属性关联,互斥量与互斥量属性关联,等等)
(2)有一个初始化函数,把属性值设为默认值
(3)有一个销毁属性对象的函数(反初始化)
(4)每个属性都有一个获取属性值的函数
(5)每个属性都有一个设置属性值的函数
1.线程属性
线程属性有:detachstate---线程的分离状态属性
stackaddr---线程栈的最低地址
stacksize---线程栈的最小长度(字节数)
guardsize---线程栈末尾的警戒缓冲区大小
pthread_attr_t:线程属性变量类型
int pthread_attr_init(pthread_attr_t *attr);//初始化
int pthread_attr_destroy(pthread_attr_t *attr);//反初始化
int pthread_attr_getdetachstate(const pthread_attr_t *restrict attr, int *detachstate);//获取分离状态
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);//设置分离状态
detachstate只能为:PTHREAD_CREATE_DETACHED,以分离状态启动线程;或者PTHREAD_CREATE_JOINABLE,正常启动线程
如果在创建线程时就知道不需要了解线程的终止状态,可以设置让线程属于分离状态;处于分离状态的线程,退出时,可回收其资源
int pthread_attr_getstack(const pthread_attr_t *restrict attr, void **restrict stackaddr, size_t *restrict stacksize);//获取线程栈最低地址
int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize);//设置线程栈最低地址
int pthread_attr_getstacksize(const pthread_attr_t *restrict attr, size_t *restrict stacksize);//获取线程栈最小长度
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);//设置线程栈最小长度
int pthread_attr_getguardsize(const pthread_attr_t *restrict attr, size_t *restrict guardsize);//获取警戒缓冲区大小
int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);//设置警戒缓冲区大小
所有函数:成功,返回0;出错,返回错误编号
程序练习:设置线程分离状态,并获取线程各项属性。
#include <stdio.h> #include <pthread.h> #include <stdlib.h> #define ERR_EXIT(m)\ {\ perror(m);\ exit(EXIT_FAILURE);\ } void *thread_func(void *arg) { pthread_attr_t attr; pthread_getattr_np(pthread_self(), &attr);//获取已创建线程的属性 int detachstate;//线程的分离状态 void *stackaddr;//线程栈的最低地址 size_t stacksize;//线程栈的最小长度 size_t guardsize;//线程栈的警戒缓冲区大小 size_t size; if (pthread_attr_getdetachstate(&attr, &detachstate) != 0)//获取线程分离状态 ERR_EXIT("pthread_attr_getdetachstate() error"); if (pthread_attr_getstack(&attr, &stackaddr, &size) != 0)//获取线程栈最低地址 ERR_EXIT("pthread_attr_getstack() error"); if (pthread_attr_getstacksize(&attr, &stacksize) != 0)//获取线程栈最小长度 ERR_EXIT("pthread_attr_getstacksize() error"); if (pthread_attr_getguardsize(&attr, &guardsize) != 0)//获取线程栈警戒缓冲区大小 ERR_EXIT("pthread_attr_getguardsize() error"); if (detachstate == PTHREAD_CREATE_DETACHED) printf("detachstate = PTHREAD_CREATE_DETACHED\n"); if (detachstate == PTHREAD_CREATE_JOINABLE) printf("detachstate = PTHREAD_CREATE_JOINABLE\n"); printf("stackaddr = %p\n", stackaddr);//%p printf("stacksize = %d\n", stacksize); printf("size = %d\n", size); printf("guardsize = %d\n", guardsize); return((void *)0); } int main() { pthread_t tid; pthread_attr_t attr; if (pthread_attr_init(&attr) != 0)//初始化线程属性 ERR_EXIT("pthread_attr_init() error"); if (pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED) != 0)//设置线程分离状态 ERR_EXIT("pthread_attr_setdetachstate() error"); if (pthread_create(&tid, &attr, thread_func, NULL) != 0) ERR_EXIT("pthread_create() error"); sleep(1); exit(0); }结果:
分析:
在线程的起始地址函数里,通过pthread_getattr_np来获取指定线程ID的线程属性
2.互斥量属性
互斥量属性用pthread_mutexattr_t结构表示,有3种属性:进程共享属性、健壮属性、类型属性int pthread_mutexattr_init(pthread_mutexattr_t *attr);//初始化
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);//反初始化
进程共享属性
有两种值:PTHREAD_PROCESS_PRIVATE和PTHREAD_PORCESS_SHARED在进程中,多个线程访问同一个同步对象,此时互斥量虚设置为PTHREAD_PROCESS_PRIVATE
存在这样的机制:允许相互独立的多个进程把同一个内存数据块映射到它们各自独立的地址空间中,此时也需要进行同步。如果进程共享互斥量属性设置为PTHREAD_PROCESS_SHARED,则从多个进程彼此之间共享的内存数据块中分配的互斥量就可以用于这些进程的同步。(意味着互斥量可用于进程间同步)
总结:同一进程内的多线程间互斥量同步用PTHREAD_PROCESS_PRIVATE,多进程间互斥量同步用PTHREAD_PROCESS_SHARED
int pthread_mutexattr_getpshared(const pthread_mutexattr_t *restrict attr, int *restrict pshared);//获取进程共享属性
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared);//设置进程共享属性
健壮属性(没弄懂)
有两种值:PTHREAD_MUTEX_STALLED和PTHREAD_MUTEX_ROBUSTPTHREAD_MUTEX_STALLED:意味着持有互斥量的进程终止时不需要采取特别的动作,结果一般是未定义的(意思就是不会对互斥量解锁)
PTHREAD_MUTEX_ROBUST:对互斥量进行恢复
int pthread_mutexattr_getrobust(const pthread_mutexattr_t *restrict attr, int *restrict robust);//获取
int pthread_mutexattr_setrobust(pthread_mutexattr_t *attr, int robust);//设置
类型属性
4种类型:互斥量类型 | 用途 | 未解锁时再次加锁? | 不 占用时解锁? | 已解锁时再次解锁? |
PTHREAD_MUTEX_NORMAL | 标准类型,不做任何检查 | 死锁 | 未定义 | 未定义 |
PTHREAD_MUTEX_ERRORCHECK | 提供错误检查 | 返回错误 | 返回错误 | 返回错误 |
PTHREAD_MUTEX_RECURSIVE | 避免死锁 | 允许 | 返回错误 | 返回错误 |
PTHREAD_MUTEX_DEFAULT | 请求默认语义 | 未定义 | 未定义 | 未定义 |
int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type);//设置
程序练习:互斥量1类型属性设置为标准类型(多次加锁会造成死锁),互斥量2类型属性设置为允许多次加锁,创建两个线程,分别对两个互斥量加两次锁,输出结果。
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#define ERR_EXIT(m)\
{\
perror(m);\
exit(EXIT_FAILURE);\
}
int g_first = 3;
int g_second = 9;
pthread_mutex_t mutex1;//互斥量
pthread_mutex_t mutex2;
void *thread_func1(void *arg)
{
printf("in thread1, now, lock mutex1\n");
pthread_mutex_lock(&mutex1);//锁住互斥量
printf("now, lock mutex1 again\n");
pthread_mutex_lock(&mutex1);//再次锁住互斥量
printf("g_first = %d\n", g_first);
pthread_mutex_unlock(&mutex1);
}
void *thread_func2(void *arg)
{
printf("in thread2, now, lock mutex2\n");
pthread_mutex_lock(&mutex2);//锁住互斥量
printf("now, lock mutex2 again\n");
pthread_mutex_lock(&mutex2);//再次锁住互斥量
printf("g_second = %d\n", g_second);
pthread_mutex_unlock(&mutex2);
}
int main()
{
pthread_mutexattr_t attr1;//互斥量属性
pthread_mutexattr_t attr2;
if (pthread_mutexattr_init(&attr1) != 0)//初始化互斥量属性
ERR_EXIT("pthread_mutexattr_init() error");
if (pthread_mutexattr_init(&attr2) != 0)
ERR_EXIT("pthread_mutexattr_init() error");
if (pthread_mutexattr_settype(&attr1, PTHREAD_MUTEX_NORMAL) != 0)//默认互斥量类型
ERR_EXIT("pthread_mutexattr_settype() error");
if (pthread_mutexattr_settype(&attr2, PTHREAD_MUTEX_RECURSIVE) != 0)//允许多次加锁
ERR_EXIT("pthread_mutexattr_settype() error");
if (pthread_mutex_init(&mutex1, &attr1) != 0)//用互斥量属性来初始化互斥量
ERR_EXIT("pthread_mutex_init() error");
if (pthread_mutex_init(&mutex2, &attr2) != 0)
ERR_EXIT("pthread_mutex_init() error");
pthread_t tid1, tid2;
pthread_attr_t thread_attr;//线程属性
if (pthread_attr_init(&thread_attr) != 0)//初始化线程属性
ERR_EXIT("pthread_attr_init() error");
if (pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED) != 0)//设置线程分离状态
ERR_EXIT("pthread_attr_setdetachstate() error");
if (pthread_create(&tid1, &thread_attr, thread_func1, NULL) != 0)//创建线程1
ERR_EXIT("pthread_create() error");
if (pthread_create(&tid2, &thread_attr, thread_func2, NULL) != 0)//创建线程2
ERR_EXIT("pthread_create() error");
sleep(5);
exit(0);
}
结果:
分析:
1.因为互斥量mutex1的类型属性为PTHREAD_MUTEX_NORMAL,所以当对mutex1再次加锁时,造成死锁。而对mutex2再次加锁则不会,因为它的类型属性为PTHREAD_MUTEX_RECURSIVE。5秒过后由于main主线程调用exit退出,所以整个进程退出。
2.因为事先知道不需要获取线程1,2的退出码,所以将线程的属性设置为PTHREAD_CREATE_DETACHED
3.设置线程属性和互斥量属性以及后面的读写锁、条件变量等属性时,一定要先进行init初始化,否则程序运行时会发“段错误”
3.读写锁属性
读写锁支持的唯一属性是进程共享属性(意味着读写锁也可用于进程间同步)。它与互斥量的进程共享属性时相同的。int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);//初始化
int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);//反初始化
int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *restrict attr, int *restrict pshared);//获取
int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared);//设置
4.条件变量属性
有两个属性:进程共享属性(同样意味着条件变量也可用于进程间同步)和时钟属性时钟属性控制计算pthread_cond_timedwait函数的超时参数(tsptr)时采用的是哪个时钟
int pthread_condattr_init(pthread_condattr_t *attr);
int pthread_condattr_destroy(pthread_condattr_t *attr);
int pthread_condattr_getpshared(const pthread_condattr_t *restrict attr, int *restrict pshared);
int pthread_condattr_setpshared(pthread_condattr_t *attr, int pshared);
int pthread_condattr_getclock(const pthread_condattr_t *restrict attr, clockid_t *restrict clock_id);
int pthread_condattr_setclock(pthread_condattr_t *attr, clockid_t clock_id);
5.屏障属性
支持进程共享属性(同样......)int pthread_barrierattr_init(pthread_barrierattr_t *attr);
int pthread_barrierattr_destroy(pthread_barrierattr_t *attr);
int pthread_barrierattr_getpshared(const pthread_barrierattr_t *restrict attr, int *restrict pshared);
int pthread_barrierattr_setpshared(pthread_barrierattr_t *attr, int pshared);
6.重入
多个控制线程在相同的时间有可能调用相同的函数,如果一个函数在相同的时间点可以被多个线程安全的调用,则称该函数是线程安全的。很多函数并不是线程安全的,因为它们返回的数据存放在静态缓冲区中。通过修改接口,要求调用者自己提供缓冲区,从而每个线程可以使用不同的缓冲区,从而避免其他线程的干扰可以使函数变为线程安全。
线程安全的函数,并不能对信号处理程序来说该函数也是可重入的。如果函数对异步信号处理程序的重入是安全的,就可以说函数是异步信号安全的。
下面的getenv是一个不可重入的函数,因为所有调用getenv的线程返回的字符都存储在同一个静态缓冲区中。
#include <stdio.h>
#include <string.h>
#include <pthread.h>
static char envbuf[1024];
extern char **environ;
char *getenv(const char *s);
void *pthread_func1(void *arg);
void *pthread_func2(void *arg);
int main()
{
pthread_t tid1, tid2;
void *tret;
pthread_create(&tid1, NULL, pthread_func1, NULL);
pthread_create(&tid2, NULL, pthread_func2, NULL);
pthread_join(tid1, &tret);
printf("thread 1 exit code: %ld\n", (long)tret);
pthread_join(tid2, &tret);
printf("thread 2 exit code: %ld\n", (long)tret);
exit(0);
}
char *getenv(const char *s)
{
int len = strlen(s);
int i;
for (i = 0; environ[i] != NULL; i++)
{
if ((strncmp(s, environ[i], len) == 0) && (environ[i][len] == '='))
{
strcpy(envbuf, &environ[i][len+1]);
return envbuf;
}
}
}
void *pthread_func1(void *arg)
{
printf("thread 1 start\n");
printf("HOME=%s\n", getenv("HOME"));
printf("thread 1 exit\n");
pthread_exit((void *)1);
}
void *pthread_func2(void *arg)
{
printf("thread 2 start\n");
printf("SHELL=%s\n", getenv("SHELL"));
printf("thread 2 exit\n");
pthread_exit((void *)2);
}
使用flockfile和ftrylockfile获取给定FILE对象关联的锁,这个锁是递归的。
int ftrylockfile(FILE *fp);
Returns: 0 if OK, nonzero if lock can’t be acquired
void flockfile(FILE *fp);
void funlockfile(FILE *fp);
为了避免标准I/O在一次一个字符操作时候频繁的获取锁开销,出现了不加锁版本的基于字符的标准I/O例程。
int getchar_unlocked(void);
int getc_unlocked(FILE *fp);
成功:返回下一个字符;出错或到文件尾,返回EOF
int putchar_unlocked(int c);
int putc_unlocked(int c, FILE *fp);
成功,返回c;出错,返回EOF
7.线程私有数据
相关比较好的文章:线程私有数据、线程私有数据pthread_key_t:键类型
pthread_once_t:pthread_once()参数,不能是局部变量(如:全局变量或静态变量),只能被初始化为PTHREAD_ONCE_INIT
pthread_once_t initflag = PTHREAD_ONCE_INIT;
int pthread_key_create(pthread_key_t *keyp, void (*destructor)(void *));//创建一个键,并设置为该键关联一个析构函数
int pthread_once(pthread_once_t *initflag, void (*initfn)(void));//确保只调用一次initfn函数,initfn函数将pthread_key_create函数封装,从而确保只调用一次....create函数
成功,返回0;出错,错误编号
void *pthread_getspecific(pthread_key_t key);
返回值:线程特定数据;若没有值与该键关联,则返回NULL
int pthread_setspecific(pthread_key_t key, const void *value);//把键和特定数据相关联
int pthread_key_delete(pthread_key_t key); //取消线程与特定数据之间的联系
成功,返回0;出错,错误编号
使用这些函数的一般步骤是:
1.封装pthread_key_create、设置键和析构函数
2.调用pthread_once函数(确保只产生一次键)
3.调用pthread_getspecific()函数(查看返回值)
4.分配内存(假如没有特定数据与键关联)
5.调用pthread_setspecific()函数(将键与特定数据相关联)
6.设置特定数据
程序练习:创建一个线程,为其设置私有数据。
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#define ERR_EXIT(m)\
{\
perror(m);\
exit(EXIT_FAILURE);\
}
pthread_once_t once = PTHREAD_ONCE_INIT;//pthread_once参数用
pthread_key_t key;//键
//线程析构函数
void destructor(void *arg)
{
printf("this is destructor for thread\n");
}
//线程只被调用一次的函数
void func(void)
{
if (pthread_key_create(&key, destructor) != 0)//创建一个键
ERR_EXIT("pthread_key_create() error");
}
void *thread_func(void *arg)
{
if (pthread_once(&once, func) != 0)//1.确保pthread_key_create()只被调用一次
ERR_EXIT("pthread_once() error");
int *pval;
pval = (int *)pthread_getspecific(key);//2.查看与该键关联的数据,若无,则返回NULL
if (pval == NULL)
pval = (int *)malloc(sizeof(int));//3.为与该键关联的数据分配内存
if (pthread_setspecific(key, pval) != 0)//4.将键与特定数据相关联
ERR_EXIT("pthread_setspecific() error");
*pval = 1;//5.添加数据
int value = *(int *)pthread_getspecific(key);//6.查看与键关联的数据
printf("thread specific data = %d\n", value);
//pthread_key_delete(key);//解除键与数据之间的关系,则不激活析构函数
//value = *(int *)pthread_getspecific(key);
//printf("after delete, thread specific data = %d\n", value);//会发生段错误
pthread_exit((void *)0);
}
int main()
{
pthread_t tid;
if (pthread_create(&tid, NULL, thread_func, NULL) != 0)
ERR_EXIT("pthread_create() error");
if (pthread_join(tid, NULL) != 0)
ERR_EXIT("pthread_join() error");
exit(0);
}
结果:
分析:
1.析构函数的参数是 void *;封装pthread_key_create函数的函数的参数是void。注意区分
2.使用这些函数的一般步骤是:封装pthread_key_create、设置键和析构函数-->调用pthread_once函数(确保只产生一次键)-->调用pthread_getspecific()函数(查看返回值)--->分配内存(假如没有特定数据与键关联)--->调用pthread_setspecific()函数(将键与特定数据相关联)--->设置特定数据
3.pthread_getspecific函数的返回值是void *,所以需要进行类型转换
4.pthread_key_delete解除键与数据关系之后,线程退出时则不会激活析构函数,再取数据时,因为是空指针,所以打印值会出现段错误
程序练习:创建两个线程1,2。线程2读线程1的私有数据,然后线程2在相同的键上设置自己的私有数据:
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#define ERR_EXIT(m)\
{\
perror(m);\
exit(EXIT_FAILURE);\
}
pthread_once_t once = PTHREAD_ONCE_INIT;//pthread_once参数用
pthread_key_t key;//键
//线程析构函数
void destructor(void *arg)
{
printf("this is destructor\n");
}
//线程只被调用一次的函数
void func(void)
{
if (pthread_key_create(&key, destructor) != 0)//和线程1用相同的键,创建一个键
ERR_EXIT("pthread_key_create() error");
}
void *thread_func1(void *arg)
{
if (pthread_once(&once, func) != 0)//确保pthread_key_create()只被调用一次
ERR_EXIT("pthread_once() error");
int *pval;
pval = (int *)pthread_getspecific(key);//查看与该键关联的数据,若无,则返回NULL
if (pval == NULL)
pval = (int *)malloc(sizeof(int));//为与该键关联的数据分配内存
if (pthread_setspecific(key, pval) != 0)//将键与特定数据相关联
ERR_EXIT("pthread_setspecific() error");
*pval = 1;//添加数据
int value = *(int *)pthread_getspecific(key);//查看与键关联的数据
printf("thread 1 specific data = %d\n", value);
pthread_exit((void *)0);
}
void *thread_func2(void *arg)
{
sleep(1);//让线程1先完成工作
int *pthread1_val;
pthread1_val = (int *)pthread_getspecific(key);//线程2读线程1的私有数据
if (pthread1_val == NULL)
printf("can't read data from thread1's specific data\n");
//线程2在和线程1相同的键上关联上自己的数据
if (pthread_once(&once, func) != 0)//确保pthread_key_create()只被调用一次
ERR_EXIT("pthread_once() error");
int *pval = (int *)pthread_getspecific(key);
if (pval == NULL)
pval = (int *)malloc(sizeof(int));
if (pthread_setspecific(key, pval) != 0)
ERR_EXIT("pthread_setspecific() error");
*pval = 5;
int value = *(int *)pthread_getspecific(key);//查看与键关联的数据
printf("thread 2 specific data = %d\n", value);
pthread_exit((void *)0);
}
int main()
{
pthread_t tid1, tid2;
if (pthread_create(&tid1, NULL, thread_func1, NULL) != 0)
ERR_EXIT("pthread_create() error");
if (pthread_create(&tid2, NULL, thread_func2, NULL) != 0)
ERR_EXIT("pthread_create() error");
if (pthread_join(tid1, NULL) != 0)
ERR_EXIT("pthread_join() error");
if (pthread_join(tid2, NULL) != 0)
ERR_EXIT("pthread_join() error");
exit(0);
}
结果:
分析:
1.线程2无法读取线程1的私有数据
2.两个线程调用的析构函数是相同的(即使分别设置各自的析构函数,已测试)
3.数据地址是非空值,析构函数就会被调用,所以free()分配的数据内存后,则不会调用析构函数(已测试)
8.线程和信号
每个线程都有自己的信号屏蔽字,但是信号的处理是进程中所有线程共享的。这意味着单个线程可以阻止某些信号,但当某个线程修改了与某个给定信号相关的处理行为以后,所有的线程都必须共享这个行为的改变。sigprocmask的行为在多线程中并没有定义,线程必须使用pthread_sigmask
#include <signal.h>
int pthread_sigmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);//和sigprocmask参数意义一样
成功:0;错误:错误编号
线程可以通过调用sigwait等待一个或多个信号的出现
int sigwait(const sigset_t *restrict set, int *restrict signop);
成功:0;错误:错误编号
如果信号集中的某个信号在sigwait调用时处于挂起状态,那么sigwait将无阻塞的返回。在返回之前,sigwait将从进程中移除那些处于挂起等待状态的信号。
要把信号发送给线程,可以调用pthread_kill
int pthread_kill(pthread_t thread, int signo);
成功:0;错误:错误编号
9.线程和fork
父进程调用fork为子进程创建了整个进程地址空间的副本,子进程从父进程那里继承了所有互斥量、读写锁和条件变量的状态。如果父进程包括多个线程,子进程在fork返回以后,如果紧接着不马上调用exec的话,就需要清理锁。在子进程内部只存在一个线程,它是由父进程中调用fork的线程的副本构成的,父进程中的线程占有锁,则子进程同样占有锁,但是子进程不包含占有锁的线程的副本。通过pthread_atfork函数建立fork处理程序清除锁状态。函数原型如下:int pthread_atfork(void (*prepare)(void), void (*parent)(void),void (*child)(void));
prepare处理程序由父进程在fork创建子进程前调用,获取父进程定义的所有锁。parent处理程序在fork创建子进程以后,但在fork返回之前在父进程环境调用,对prepare处理程序获得的所有锁进行解锁,child处理程序在fork返回之前在子进程环境中调用,也必须释放prepare处理程序获得的所有锁。parent和child处理程序与它们注册时顺序相同,prepare处理程序调用则与注册时的顺序相反。
写个程序演示如何使用pthread_atfork和fork处理程序。程序如下:
#include <stdio.h> #include <pthread.h> #include <signal.h> pthread_mutex_t lock1 = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t lock2 = PTHREAD_MUTEX_INITIALIZER; void prepare(void) { printf("preparing locks...\n"); pthread_mutex_lock(&lock1); pthread_mutex_lock(&lock2); } void parent(void) { printf("parent unlocking locks...\n"); pthread_mutex_unlock(&lock1); pthread_mutex_unlock(&lock2); } void child(void) { printf("child unlocking locks...\n"); pthread_mutex_unlock(&lock1); pthread_mutex_unlock(&lock2); } void *thr_fn(void *arg) { printf("thread started...\n"); pause(); return(0); } int main() { pid_t pid; pthread_t tid; pthread_atfork(prepare, parent, child); pthread_create(&tid, NULL, thr_fn, NULL); sleep(2); printf("parent about to fork...\n"); pid = fork(); if (pid == 0) printf("child returned from fork\n"); else printf("parent returned from fork\n"); exit(0); }结果:
相关文章推荐
- 《APUE》读书笔记—第十二章线程控制
- APUE函数笔记十: 线程控制
- APUE笔记 线程控制
- 《APUE》读书笔记第十二章-线程控制
- 《APUE》读书笔记—第十二章线程控制
- [APUE]第十二章 线程控制
- 《深入理解Java虚拟机》笔记--第十二章、Java内存模型与线程
- java学习笔记1018---线程的控制
- Linux进程线程学习笔记:进程控制
- 线程控制——《UNIX环境高级编程》笔记
- [APUE]第十二章 线程控制
- APUE Chapter 12笔记:Unix下线程的控制(III)
- apue学习第十九天(1)——可重入与线程安全(第十二章)
- APUE Chapter 12笔记:Unix下线程的控制(I)
- UNIX环境编程学习笔记(27)——多线程编程(二):控制线程属性
- android 学习笔记4——post请求+线程控制
- AUPE学习第十二章------线程控制
- UNIX环境高级编程学习之第十二章线程控制-可重入(线程安全)的getenv方法
- UNIX环境高级编程学习之第十二章线程控制-以分离状态创建线程
- APUE函数笔记九: 线程