linux线程学习(5)

来源:互联网 发布:1吨铀能发多少电 知乎 编辑:程序博客网 时间:2024/05/17 17:15

线程的高级属性

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);

进程共享属性需要检测系统是否支持,可以检测宏_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,记得编译时连接lrtint 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;}

这里写图片描述
解析:父进程先运行(子进程有进行睡眠),并加锁互斥锁,然后改变共享内存中的内容,父进程睡眠->子进程运行,子进程加互斥锁阻塞,父进程睡眠结束—>父进程运行解锁并打印共享内存的内容,—>子进程加锁,改变共享内存内容,加锁打印。因此父子进程打印的内容是不一样的。如果没有进行互斥量的加锁同步,打印的内容是一样的。

  • 读写锁的属性:与互斥锁类似。只是具体实现函数不同。
  • 条件变量的属性:与互斥锁类似。只是具体实现函数不同。
原创粉丝点击