Linux学习之多线程编程(Linux线程的高级控制)

言之者无罪,闻之者足以戒。 ——《诗序》

三、Linux线程的高级控制

1、一次性初始化

有些事需要且只能执行一次(比如互斥量初始化)。通常当初始化应用程序时,可以比较容易地将其放在main函数中。但当你写一个库函数时,就不能在main里面初始化了,你可以用静态初始化,但使用一次初始(pthread_once_t)会比较容易些。

首先要定义一个pthread_once_t变量,这个变量要用宏PTHREAD_ONCE_INIT初始化。然后创建一个与控制变量相关的初始化函数。pthread_once_t once_control = PTHREAD_ONCE_INIT;

void init_routine()
{
 //初始化互斥量
 //初始化读写锁
 ......
}

接下来就可以在任何时刻调用pthread_once函数

(1)、pthread_once一次性初始化函数

int pthread_once(pthread_once_t* once_control, void (*init_routine)(void))

第一个参数:once_control(初始化函数调用情况的标志)

第二个参数:初始化执行函数

返回值:成功返回0,失败返回错误码

功能:此函数使用初值为PTHREAD_ONCE_INIT的once_control变量保证init_routine()函数在本进程执行序列中仅执行一次。在多线程编程环境下,尽管pthread_once()调用会出现在多个线程中,init_routine()函数仅执行一次,究竟在哪个线程中执行是不定的,是由内核调度来决定。

Linux Threads使用互斥锁和条件变量保证由pthread_once()指定的函数执行且仅执行一次。实际"一次性函数"的执行状态有三种:

NEVER(0)、IN_PROGRESS(1)、DONE (2),用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

直接给出代码:

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>

pthread_t tid;
pthread_once_t once=PTHREAD_ONCE_INIT;

void thread_init()
{
        printf("I am in thread 0x%x\n",tid);
}

void *thread_fun1(void *arg)
{
        tid = pthread_self();
        printf("I am thread 0x%x\n",tid);
        printf("once is %d\n",once);
        pthread_once(&once,thread_init);
        printf("once is %d\n",once);

        return NULL;
}
void *thread_fun2(void *arg)
{
        sleep(2);
        tid = pthread_self();
        printf("I am thread 0x%x\n",tid);
//      printf("once is %d\n",once);
        pthread_once(&once,thread_init);
//      printf("once is %d\n",once);

        return NULL;
}


int main()
{
        pthread_t tid1, tid2;
        int err;

        err = pthread_create(&tid1, NULL, thread_fun1, NULL);
        if(err != 0)
        {
                printf("create new thread 1 failed\n");
                return ;
        }
        err = pthread_create(&tid2, NULL, thread_fun2, NULL);
        if(err != 0)
        {
                printf("create new thread 1 failed\n");
                return ;
        }


        pthread_join(tid1, NULL);
        pthread_join(tid2, NULL);

        return 0;
}

2、线程的属性

线程的属性用pthread_attr_t类型的结构表示,在创建线程的时候可以不用传入NULL,而是传入一个pthread_attr_t结构,有用户自己来配置线程的属性。pthread_attr_t类型对应用程序是不透明的,也就是说应用程序不需要了解有关属性对象内部结构的任何细节,因而可以增加程序的可移植性。

线程属性:

名称 描述
detachstate 线程的分离状态
guardsize 线程栈末尾的警戒区域大小(字节数)
 stackaddr 线程栈的最低地址
stacksize 线程栈的大小(字节数)

并不是所有的系统都支持线程的这些属性,因此你需要检查当前系统是否支持你设置的属性。当然还有一些属性不包含在pthread_attr_t结构中,例如:线程的可取消状态、取消类型、并发度

(1)、pthread_attr_init线程属性初始化函数

int pthread_attr_init(pthread_attr_t *attr)

参数:要初始化的属性

返回值:成功返回0 ,失败返回错误码

(2)、pthread_attr_destory线程属性销毁函数

int pthread_attr_destroy(pthread_attr_t *attr)

参数:要初始化的属性

返回值:成功返回0 ,失败返回错误码

如果在调用pthread_attr_init初始化属性的时候分配了内存空间,那么pthread_attr_destroy将释放内存空间。除此之外,pthread_atty_destroy还会用无效的值初始化pthread_attr_t对象,因此如果该属性对象被误用,会导致创建线程失败。

注意:pthread_attr_t结构在使用之前需要初始化,使用完之后需要销毁

什么叫做分离?

分离一个正在运行的线程并不影响它,仅仅是通知当前系统该线程结束时,其所属的资源可以回收。一个没有被分离的线程在终止时会保留它的虚拟内存,包括他们的堆栈和其他系统资源,有时这种线程被称为“僵尸线程”。创建线程时默认是非分离的。如果线程具有分离属性,线程终止时会被立刻回收,回收将释放掉所有在线程终止时未释放的系统资源和进程资源,包括保存线程返回值的内存空间、堆栈、保存寄存器的内存空间等。

(3)、pthread_attr_setdetachstate设置线程的分离属性

int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)

第一个参数:要设置的分离状态属性

第二个参数:状态设置(有两种值:PTHREAD_CREATE_DETACHED分离的、PTHREAD_CREATE_JOINABLE 非分离的,可连接的)

返回值:成功返回0 ,失败返回错误码

(4)、pthread_attr_getdetachstate分离状态属性获取函数

 int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate)

第一个参数:要设置的分离状态属性

第二个参数:已经设置的分离状态属性

返回值:成功返回0 ,失败返回错误码

设置线程分离属性的步骤
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 <string.h>
#include <signal.h>
#include <errno.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>


void *thread_fun1(void *arg)
{

        printf("I'm new thread 1\n");

        return (void *)1;
}

void *thread_fun2(void *arg)
{
        printf("I'm new thread 2\n");

        return (void *)2;
}

int main()
{
        pthread_t tid1, tid2;
        int err;
        int *flag;
        //定义属性变量
        pthread_attr_t attr;
        //初始化属性
        pthread_attr_init(&attr);
        //设置分离状态属性,置为已分离
        pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
        //获取分离状态的属性    
        pthread_attr_getdetachstate(&attr,flag );
        printf("flag is %p\n",flag);

        err = pthread_create(&tid1, &attr, thread_fun1, NULL);
        if(err)
        {
                printf("create new thread 1 failed\n");
                return;
        }

        err = pthread_create(&tid2, NULL, thread_fun2, NULL);
        if(err)
        {
                printf("create new thread 2 failed\n");
                return;
        }

        //连接线程1
        err = pthread_join(tid1, NULL);
        if(!err)
                printf("join thread 1 success\n");
        else
                printf("join thread 1 failed\n");
        //连接线程2
        err = pthread_join(tid2, NULL);
        if(!err)
                printf("join thread 2 success\n");
        else
                printf("join thread 2 failed\n");

        //销毁attr
        pthread_attr_destroy(&attr);


        return 0;
}

对于进程来说,虚拟地址空间的大小是固定的,进程中只有一个栈,因此它的大小通常不是问题。但对线程来说,同样的虚拟地址被所有的线程共享。如果应用程序使用了太多的线程,致使线程栈累计超过可用的虚拟地址空间,这个时候就需要减少线程默认的栈大小。另外,如果线程分配了大量的自动变量或者线程的栈帧太深,那么这个时候需要的栈要比默认的大。如果用完了虚拟地址空间,可以使用malloc或者mmap来为其他栈分配空间,并修改栈的位置。

(5)、pthread_attr_setstack修改栈的属性

int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize)

第一个参数:栈属性

第二个参数:栈的内存单元最低地址

第三个参数:栈的大小

返回值:成功返回0,失败返回错误码

(6)、phread_attr_getstack获取栈属性

int pthread_attr_getstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize)

第一个参数:栈属性

第二个参数:栈的内存单元最低地址

第三个参数:栈的大小

返回值:成功返回0,失败返回错误码

参数stackaddr是栈的内存单元最低地址,参数stacksize是栈的大小。要注意stackaddr并不一定是栈的开始,对于一些处理器,栈的地址是从高往低的,那么这是stackaddr是栈的结尾。

当然也可以单独获取或者修改栈的大小,而不去修改栈的地址。对于栈大小设置,不能小于PTHREAD_STACK_MIN(需要头文件limit.h)

(7)、pthread_attr_setstacksize设置栈的大小

int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize)

第一个参数:栈属性

第二个参数:要设置的栈大小

返回值:成功返回0,失败返回错误码

(8)、pthread_attr_getstacksize获取栈的大小

int pthread_attr_getstacksize(pthread_attr_t *attr, size_t *stacksize)

第一个参数:栈属性

第二个参数:要获取的栈大小

返回值:成功返回0,失败返回错误码

对于栈大小的设置,在创建线程之后,还可以修改。

对于遵循POSIX标准的系统来说,不一定要支持线程的栈属性,因此你需要检查

1)、在编译阶段使用

_POSIX_THREAD_ATTR_STACKADDR 和 _POSIX_THREAD_ATTR_STACKSIZE符号来检查系统

_POSIX_THREAD_ATTR_STACKSIZE符号来检查系统在/usr/include/bits/posix_opt.h(可以用#ifdef和#endif来实现)

2)、在运行阶段

把_SC_THREAD_ATTR_STACKADD和 _SC_THREAD_THREAD_ATTR_STACKSIZE传递给sysconf函数检查系统对线程栈属性的支持

线程属性guardsize控制着线程栈末尾以后用以避免栈溢出的扩展内存的大小,这个属性默认是PAGESIZE个字节。你可以把它设为0,这样就不会提供警戒缓冲区。同样的,如果你修改了stackaddr,系统会认为你自己要管理栈,警戒缓冲区会无效。

(9)、pthread_attr_setguardsize设置设置guardsize

int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize)

第一个参数:栈属性

第二个参数:扩展内存的大小

返回值:成功返回0,失败返回错误码

(10)、pthread_attr_getguardsize获取guardsize

int pthread_attr_getguardsize(pthread_attr_t *attr, size_t *guardsize)

第一个参数:栈属性

第二个参数:扩展内存的大小

返回值:成功返回0,失败返回错误码

下面来看一下程序,熟悉一下上面说的函数:

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>

pthread_attr_t attr;
void *thread_fun(void *arg)
{
        size_t stacksize;
#ifdef _POSIX_THREAD_ATTR_STACKSIZE
        pthread_attr_getstacksize(&attr,&stacksize);
        printf("new thread stack size is %d\n",stacksize);
        pthread_attr_setstacksize(&attr,17684);
        pthread_attr_getstacksize(&attr,&stacksize);
        printf("new thread stack size is %d\n",stacksize);
#endif
        return (void *)1;
}
int main()
{
        pthread_t tid;
        int err;
        pthread_attr_init(&attr);
        pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

#ifdef _POSIX_THREAD_ATTR_STACKSIZE
        pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN);
#endif
        err = pthread_create(&tid, &attr, thread_fun, NULL);
        if(err)
        {
                printf("create new thread failed\n");
                return;
        }

        pthread_join(tid,NULL);
        return 0;
}

 

猜你喜欢

转载自blog.csdn.net/weixin_42994525/article/details/83216374