linux线程学习(5)

线程的高级属性

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,记得编译时连接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;
}

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

  • 读写锁的属性:与互斥锁类似。只是具体实现函数不同。
  • 条件变量的属性:与互斥锁类似。只是具体实现函数不同。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值