线程的高级属性
1. 一次性初始化:
- 有些事需要且只能执行一次(比如互斥量初始化)。因此有了使用一次初始(pthread_once_t);
- 首先要定义一个pthread_once_t变量,这个变量要用宏PTHREAD_ONCE_INIT初始化。然后创建一个与控制变量相关的初始化函数pthread_once_t once_control = PTHREAD_ONCE_INIT;
- 接下来就可以在任何时刻调用pthread_once函数
int pthread_once(pthread_once_t* once_control, void (*init_routine)(void));
//功能:本函数使用初值为PTHREAD_ONCE_INIT的once_control变量保证init_routine()函数在本进程执行序列中仅执行一次。在多线程编程环境下,尽管pthread_once()调用会出现在多个线程中,init_routine()函数仅执行一次,究竟在哪个线程中执行是不定的,是由内核调度来决定。
由pthread_once()指定的函数init_routine执行且仅执行一次
- 用once_control来表示pthread_once()的执行状态:
1、如果once_control初值为0,那么 pthread_once从未执行过,init_routine()函数会执行。
2、如果once_control初值设为1,则由于所有pthread_once()都必须等待其中一个激发”已执行一次”信号, 因此所有pthread_once ()都会陷入永久的等待中,init_routine()就无法执行
3、如果once_control设为2,则表示pthread_once()函数已执行过一次,从而所有pthread_once()都会立即 返回,init_routine()就没有机会执行
当pthread_once函数成功返回,once_control就会被设置为2
实例:创建两个线程分别调用pthread_once函数,执行初始化函数分别打印once_contrl的值。
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
pthread_once_t once_contrl=PTHREAD_ONCE_INIT;//定义pthread_once_t变量,在pthread_once函数中用于还回init_routine函数执行状态
void init_routine(void)
{
//这个函数只会执行一次
printf("我是初始化函数,我只执行一次\n");
}
void * init_func1(void * arg)
{
printf("this is pthread %d excute,once_contrl = %d\n",(int)arg,once_contrl);
pthread_once(&once_contrl,init_routine);
printf("the once_contrl = %d\n",once_contrl);
return NULL;
}
void * init_func2(void * arg)
{
sleep(2);
printf("this is pthread %d excute,once_contrl = %d\n",(int)arg,once_contrl);
pthread_once(&once_contrl,init_routine);
printf("the once_contrl = %d\n",once_contrl);
return NULL;
}
int main()
{
pthread_t pid1,pid2;
//创建两个线程。进行一次性初始化
pthread_create(&pid1,NULL,init_func1,(void*)1);
pthread_create(&pid2,NULL,init_func2,(void*)2);
//连接线程
pthread_join(pid1,NULL);
pthread_join(pid2,NULL);
return 0;
}
运行结果:
线程1先执行打印once_contrl =0,因为这时还没调用pthread_once函数,执行初始化init_routine函数,然后调用执行,
once_control设为2,则表示pthread_once()函数已执行过一次,从而其他线程调用pthread_once()都会立即 返回,init_routine()就没有机会再执行
2. 线程属性(即我们使用pthread_create()函数中的第二个参数,与待会介绍的线程同步属性的类型不同)
- 线程的属性用pthread_attr_t类型的结构表示,在创建线程的时候可以不用传入NULL,而是传入一个pthread_attr_t结构,由用户自己来配置线程的属性
- 线程的分离属性
- 分离属性的概念:分离一个正在运行的线程并不影响它,仅仅是通知当前系统该线程结束时,其所属的资源可以回收。一个没有被分离的线程在终止时会保留它的虚拟内存,包括他们的堆栈和其他系统资源,有时这种线程被称为“僵尸线程”。创建线程时默认是非分离的
如果线程具有分离属性,线程终止时会被立刻回收,回收将释放掉所有在线程终止时未释放的系统资源和进程资源,包括保存线程返回值的内存空间、堆栈、保存寄存器的内存空间等。 - 使用方法:如果在创建线程的时候就直到不需要了解线程的终止状态,那么可以修改pthread_attr_t结构体的detachstate属性,让线程以分离状态启动。可以使用pthread_attr_setdetachstate函数来设置线程的分离状态属性。线程的分离属性有两种合法值:
PTHREAD_CREATE_DETACHED分离的
PTHREAD_CREATE_JOINABLE 非分离的,可连接的
- 分离属性的概念:分离一个正在运行的线程并不影响它,仅仅是通知当前系统该线程结束时,其所属的资源可以回收。一个没有被分离的线程在终止时会保留它的虚拟内存,包括他们的堆栈和其他系统资源,有时这种线程被称为“僵尸线程”。创建线程时默认是非分离的
- 线程的分离属性
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);
//使用pthread_attr_getdetachstate可以获得线程的分离状态属性
//设置线程分离属性的步骤
//1、定义线程属性变量pthread_attr_t attr
//2、初始化attr,pthread_attr_init(&attr)
//3、设置线程为分离或非分离 pthread_attr_setdetachstate(&attr, //detachstate)
//4、创建线程pthread_create(&tid, &attr, thread_fun, //NULL)
//所有的系统都会支持线程的分离状态属性,
实例练习
分别连接属性为非分离的线程,和属性分离的线程,查看链接结果:
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
int state;
pthread_attr_t attr1;
pthread_attr_t attr2;
void * pthread_func1(void *arg)
{ //得到线程属性中的分离属性并打印:非分离
pthread_attr_getdetachstate(&attr1,&state);
printf("this is pthread1,state = %d\n",state);
return NULL;
}
void * pthread_func2(void *arg)
{
//得到线程属性中的分离属性并打印:分离
pthread_attr_getdetachstate(&attr2,&state);
printf("this is pthread2,state = %d\n",state);
return NULL;
}
int main()
{
pthread_t pid1,pid2;
int err;
//属性初始化
pthread_attr_init(&attr1);
pthread_attr_init(&attr2);
//设置线程二的属性为分离的,线程一采用默认属性:非分离的
pthread_attr_setdetachstate(&attr2,PTHREAD_CREATE_DETACHED);
//创建两个线程,一个默认属性,一个分离属性
pthread_create(&pid1,&attr1,pthread_func1,NULL);
pthread_create(&pid2,&attr2,pthread_func2,NULL);
//连接两个线程,带有分离属性的线程,线程还回后系统自动释放资源,不需连接,连接会失败
err=pthread_join(pid1,NULL);
if(err!=0)
printf("join1 failed\n");
else
printf("join1 succeed\n");
err=pthread_join(pid2,NULL);
if(err!=0)
printf("join2 failed\n");
else
printf("join2 succeed\n");
//pthread_attr_t结构在使用之前需要初始化,使用完之后需要销毁
pthread_attr_destroy(&attr1);
pthread_attr_destroy(&attr2);
return 0;
}
结果中的0表示:PTHREAD_CREATE_JOINABLE
结果中的1表示:PTHREAD_CREATE_DETACHED
可在/usr/include/pthread.h中查看:
- 线程栈属性:这里不做介绍
3.线程的同步属性(与线程创建时的属性有区别)
- 互斥量的属性
- 就像线程有属性一样,线程的同步互斥量也有属性,比较重要的是进程共享属性和类型属性。互斥量的属性用pthread_mutexattr_t类型的数据表示,当然在使用之前必须进行初始化,使用完成之后需要进行销毁:
互斥量初始化
int pthread_mutexattr_init(pthread_mutexattr_t *attr);
互斥量销毁
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr); - 进程共享属性有两种值:
PTHREAD_PROCESS_PRIVATE,这个是默认值,同一个进程中的多个线程访问同一个同步对象
PTHREAD_PROCESS_SHARED, 这个属性可以使互斥量在多个进程中进行同步(在进程中共享,创建共享内存使得进程都能访问),如果互斥量在多进程的共享内存区域,那么具有这个属性的互斥量可以同步多进程。 - 设置互斥量进程共享属性
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared);
int pthread_mutexattr_getpshared(const pthread_mutexattr_t *restrict attr, int *restrict pshared);
- 就像线程有属性一样,线程的同步互斥量也有属性,比较重要的是进程共享属性和类型属性。互斥量的属性用pthread_mutexattr_t类型的数据表示,当然在使用之前必须进行初始化,使用完成之后需要进行销毁:
进程共享属性需要检测系统是否支持,可以检测宏_POSIX_THREAD_PROCESS_SHARED
实例练习
用互斥量实现两个进程间的同步。(如果不进行同步的话,两个进程打印的内容是一样的)。
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
//Link with -lrt,记得编译时连接lrt
int main()
{
pid_t pid;
char * shm_name1="shm_name1";
char * shm_name2="shm_name2";
char * buff;
int shm_id1,shm_id2;
pthread_mutex_t *mutex;
pthread_mutexattr_t mutexattr;
//创建共享内存1
shm_id1=shm_open(shm_name1, O_RDWR|O_CREAT,0644);
//调整共享内存大小
ftruncate(shm_id1, 100);
//映射共享内存,MAP_SHARED属性表明,对共享内存的任何修改都会影响其他进程
buff =(char *)mmap(NULL, 100, PROT_READ|PROT_WRITE, MAP_SHARED, shm_id1, 0);
//创建共享内存2
shm_id2=shm_open(shm_name2, O_RDWR|O_CREAT,0644);
//调整共享内存大小
ftruncate(shm_id2, 100);
//映射共享内存,MAP_SHARED属性表明,对共享内存的任何修改都会影响其他进程
mutex =(pthread_mutex_t *)mmap(NULL, 100, PROT_READ|PROT_WRITE, MAP_SHARED, shm_id2, 0);
pthread_mutexattr_init(&mutexattr);//初始化属性
#ifdef _POSIX_THREAD_PROCESS_SHARED//判断系统是否支持设置属性
pthread_mutexattr_setpshared(&mutexattr,PTHREAD_PROCESS_SHARED);//设置属性
#endif
pthread_mutex_init(mutex,&mutexattr);//初始化互斥量
pid=fork();//创建新进程
if(pid<0)
{
printf("fork child proccess failed\n");
return -1;
}
if(pid==0)
{
sleep(1);
pthread_mutex_lock(mutex);
strcpy(buff,"world");
pthread_mutex_unlock(mutex);
printf("i am child and buff is :%s\n",buff);
}
if(pid>0)
{
pthread_mutex_lock(mutex);
strcpy(buff,"hello");
sleep(2);
pthread_mutex_unlock(mutex);
printf("i am parents and buff is :%s\n",buff);
}
sleep(1);
//销毁互斥量,以及属性
pthread_mutex_destroy(mutex);//这里先销毁,然后再解除映射,相反的话,映射先解除了,销毁是就找不到,会发送段错误
pthread_mutexattr_destroy(&mutexattr);
//销毁共享内存
munmap(buff, 100);
//解除映射
shm_unlink(shm_name1);
//销毁共享内存
munmap(mutex, 100);
//解除映射
shm_unlink(shm_name2);
return 0;
}
解析:父进程先运行(子进程有进行睡眠),并加锁互斥锁,然后改变共享内存中的内容,父进程睡眠->子进程运行,子进程加互斥锁阻塞,父进程睡眠结束—>父进程运行解锁并打印共享内存的内容,—>子进程加锁,改变共享内存内容,加锁打印。因此父子进程打印的内容是不一样的。如果没有进行互斥量的加锁同步,打印的内容是一样的。
- 读写锁的属性:与互斥锁类似。只是具体实现函数不同。
- 条件变量的属性:与互斥锁类似。只是具体实现函数不同。