Linux多线程开发

1.线程概述

1.1 线程基本概念

与进程(process)类似,线程(thread)是允许应用程序并发执行多个任务的一种机制。一个进程可以包含多个线程。

同一个程序中的所有线程均会独立执行相同程序,且共享同一份全局内存区域,其中包括初始化数据段、未初始化数据段,以及堆内存段。

进程是CPU分配资源的最小单位,线程是操作系统调度执行的最小单位。

线程是轻量级的进程(LWP: Light Weight Process),在Linux环境下,线程的本质仍然是进程。

查看指定进程的LWP号: ps -Lf pid

1.2 线程与进程的区别

进程间的信息难以共享。由于除去只读代码段外,父子进程并没有共享内存,因此必须采用一些进程间通信方式,在进程间进行信息交换。

调用 fork() 来创建进程的代价相对较高,即便利用读时共享,写时复制技术,仍然需要复制诸如
内存页表文件描述符表之类的多种进程属性,这意味着 fork() 调用在时间上的开销依然不菲。

线程之间能够方便、快速地共享信息。只需将数据复制到共享(全局或堆)变量中即可。

创建线程比创建进程通常要10 倍甚至更多。线程间是共享虚拟地址空间的,无需采用写时复制来复制内存,也无需复制页表。

1.3线程之间共享和非共享的资源

共享资源:

  • 进程ID和父进程ID
  • 进程组ID和会话ID
  • 用户ID和用户组ID
  • 文件描述符表
  • 文件系统相关的信息:文件权限掩码(umask)、当前工作目录等
  • 虚拟地址空间(除了栈,.text)

非共享资源:

  • 线程ID
  • 信号掩码
  • 线程特有的数据
  • error 变量
  • 实时调度策略和优先级
  • 栈,本地变量和函数的调用链接信息

补充小知识:

当 Linux 最初开发时,在内核中并不能真正支持线程。但是它的确可以通过 clone()系统调用将进程作为可调度的实体。这个调用创建了调用进程( calling process )的一个拷贝,这个拷贝与调用进程共享相同的地址空间。 LinuxThreads 项目使用这个调用来完成在用户空间模拟对线程的支持。不幸的是,这种方法有一些缺点,尤其是在信号处理、调度和进程间同步等方面都存在问题。另外,这个线程模型也不符合 POSIX 的要求。

要改进 LinuxThreads ,需要内核的支持,并且重写线程库。有两个相互竞争的项目开始来满足这些要求。一个包括 IBM 的开发人员的团队开展了 NGPT Next GenerationPOSIX Threads )项目。同时 Red Hat 的一些开发人员开展了 NPTL 项目。 NGPT在 2003 年中期被放弃了,把这个领域完全留给了 NPTL 。

NPTL ,或称为 Native POSIX Thread Library ,是 Linux 线程的一个新实现,它克服了 LinuxThreads 的缺点,同时也符合 POSIX 的需求。与 LinuxThreads 相比,它在性能和稳定性方面都提供了重大的改进。

查看当前 pthread 库版本: getconf GNU_LIBPTHREAD_VERSION

2.线程的创建pthread_create

一般情况下,main函数所在的线程我们称之为主线程(main线程),其余创建的线程称之为子线程。

程序中默认只有一个进程,通过fork系统调用之后,会有两个进程,即父子进程。

程序中默认只有一个线程,即主线程,pthread_create()函数调用后,会有2个线程。

用于创建子线程的函数是pthread_create(),通过man pthread_create()可以查看详细描述。

#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, 
                   void *(*start_routine) (void *), void *arg);

功能:创建一个子线程

参数:

        thread:传出参数,线程创建成功后,子线程的线程ID被写到该变量中。

        attr : 设置线程的属性,一般使用默认值,NULL

        start_routine : 函数指针,这个函数是子线程需要处理的逻辑代码

        arg : 给第三个参数(函数指针)使用的参数

返回值:

        成功:0

        失败:thread未指定,返回错误号。这个错误号和之前errno不太一样,不能通过perror获取。获取错误号的信息:  char * strerror(int errnum);  #include<string.h>

子线程创建案例:

#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>  //sleep

void * callback(void * arg) {
    printf("child thread...\n");
    printf("arg value: %d\n", *(int *)arg);  //arg是void*,需要转换成(int *),然后解引用
    return NULL;
}

int main() {  //主线程

    pthread_t tid;

    int num = 10;

    // 创建一个子线程,执行的代码在callback 
    int ret = pthread_create(&tid, NULL, callback, (void *)&num);  //(void *)强转

    if(ret != 0) {
        char * errstr = strerror(ret);
        printf("error : %s\n", errstr);
    } 

    for(int i = 0; i < 5; i++) {
        printf("%d\n", i);
    }

    sleep(1);  //让子线程创建完,并获得CPU资源运行

    return 0;   // exit(0);
}

在使用线程的库的时候,在编译时要加上 -pthread  (这是因为这个库是第三方库

3.线程的终止pthread_exit

pthread_exit函数用于退出当前线程。当一个线程调用pthread_exit函数时,该线程立刻终止(也就是说在哪个线程中调用,就表示终止哪个线程)。其他所有的线程(包括同一进程中的其他线程)继续执行。

通过 man pthread_exit 查看具体函数描述

pthread_exit() 函数会终止调用它的线程,并通过 retval 返回一个值。如果这个线程是可加入的,那么这个值对同一个进程中调用 pthread_join() 的另一个线程是可用的。

Any clean-up handlers established by pthread_cleanup_push(3) that have not yet been popped, are popped (in the reverse of the order in which they were pushed) and executed. If the thread has any thread-specific data, then, after the clean-up handlers have been executed, the corresponding destructor functions are called, in an unspecified order.【任何由 pthread_cleanup_push(3) 建立的且尚未被弹出的清理处理程序都会被弹出(按照与它们被推入相反的顺序)并执行。如果线程有任何线程特定的数据,则在清理处理程序执行之后,以未指定的顺序调用相应的析构函数。】

When a thread terminates, process-shared resources (e.g., mutexes, condition variables, semaphores, and file descriptors) are not released, and functions registered using atexit(3) are not called.【当一个线程终止时,进程共享的资源(例如互斥量、条件变量、信号量和文件描述符)不会被释放,使用 atexit(3) 注册的函数也不会被调用。】

After the last thread in a process terminates, the process terminates as by calling exit(3) with an exit status of zero; thus, process-shared resources are released and functions registered using atexit(3) are called.【当进程中的最后一个线程终止后,进程将调用 exit(3) 函数并以零状态码终止;因此,进程共享的资源将被释放,使用 atexit(3) 注册的函数将被调用。】

#include <pthread.h>
void pthread_exit(void *retval);

功能:终止一个线程,在哪个线程中调用,就表示终止哪个线程

参数:需要传递的一个指针,作为一个返回值,可以在pthread_join()中获取到。如果不需要,可以设置为NULL。

没有返回值。

pthread_t pthread_self(void);

功能:获取当前的线程线程ID。

返回值:是pthread_t类型,可以理解为是一个长整形。

int pthread_equal(pthread_t t1, pthread_t t2);

功能:比较两个线程ID是否相等,不同的操作系统,pthread_t类型的实现不一样,有的是无符号的长整型,有的是使用结构体去实现的。

返回值: If the two thread IDs are equal, pthread_equal() returns a nonzero value; otherwise, it returns 0.

测试代码:

#include <stdio.h>
#include <pthread.h>
#include <string.h>

void * callback(void * arg) {
    printf("child thread id : %ld\n", pthread_self());
    return NULL;    // pthread_exit(NULL);
} 

int main() {

    // 创建一个子线程
    pthread_t tid;
    int ret = pthread_create(&tid, NULL, callback, NULL);

    if(ret != 0) {
        char * errstr = strerror(ret);
        printf("error : %s\n", errstr);
    }

    // 主线程,先打印0-4
    for(int i = 0; i < 5; i++) {
        printf("%d\n", i);
    }

    //主线程打印子线程的id和主线程的id
    printf("tid : %ld, main thread id : %ld\n", tid ,pthread_self());

    // 让主线程退出,当主线程退出时,不会影响其他正常运行的线程。
    pthread_exit(NULL);

    printf("main thread exit\n");  //主线程退出了,这个程序不会输出

    return 0;   // exit(0);
}

其实在这个程序中,子线程和主线程是交替执行的。如果把主线程中的for循环改成:

for(int i = 0; i < 1000; i++) {
    printf("%d\n", i);
}

可以看到,主线程打印到85的时候,子线程开始打印输出,一直到主线程退出。

4.连接已经终止的线程pthread_join

在进程中,子进程执行完毕之后需要父进程来回收它的资源(手动回收使用wait或者waitpid函数)。那么在线程中也是一样的,子线程结束之后,需要回收子线程的资源(任何的线程都可以回收其他线程的资源,一般情况下,都是主线程回收子线程的资源)。

#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);

功能:和一个已经终止的线程进行连接,回收这个子线程的资源,这个函数是个阻塞函数,调用一次只能回收一个子线程。一般在主线程中使用。

参数: pthread_t thread:需要回收的子线程的ID;retval: 接收子线程退出时的返回值。

返回值:成功返回0,失败返回非0,错误号。

测试代码:在主线程中回收子线程的资源

#include <stdio.h>
#include <pthread.h>
#include <string.h>

void * callback(void * arg) {
    printf("child thread id : %ld\n", pthread_self());
    return NULL;    // pthread_exit(NULL);
} 

int main() {

    // 创建一个子线程
    pthread_t tid;
    int ret = pthread_create(&tid, NULL, callback, NULL);

    if(ret != 0) {
        char * errstr = strerror(ret);
        printf("error : %s\n", errstr);
    }

    // 主线程,先打印0-4
    for(int i = 0; i < 5; i++) {
        printf("%d\n", i);
    }

    //主线程打印子线程的id和主线程的id
    printf("tid : %ld, main thread id : %ld\n", tid ,pthread_self());

    //主线程调用pthread_join回收子线程的资源
    pthread_join(tid, NULL);

    // 让主线程退出,当主线程退出时,不会影响其他正常运行的线程。
    pthread_exit(NULL);

    return 0;   // exit(0);
}

执行之后,输出并没有明显的变化。

为了结果明显,我们在原来的代码基础上,获取pthread_join的返回值,并判断,并在回收之后打印“回收子线程资源成功!”;

同时,在子线程中sleep(3),pthread_join是一个阻塞函数,他会等待子线程执行完(即3秒)。3秒后打印“回收子线程资源成功!”

测试代码:

#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>

void * callback(void * arg) {
    printf("child thread id : %ld\n", pthread_self());
    sleep(3);
    return NULL;    // pthread_exit(NULL);
} 

int main() {

    // 创建一个子线程
    pthread_t tid;
    int ret = pthread_create(&tid, NULL, callback, NULL);

    if(ret != 0) {
        char * errstr = strerror(ret);
        printf("error : %s\n", errstr);
    }

    // 主线程,先打印0-4
    for(int i = 0; i < 5; i++) {
        printf("%d\n", i);
    }

    //主线程打印子线程的id和主线程的id
    printf("tid : %ld, main thread id : %ld\n", tid ,pthread_self());

    //主线程调用pthread_join回收子线程的资源
    ret = pthread_join(tid, NULL);
    if(ret != 0) {
        char * errstr = strerror(ret);
        printf("error : %s\n", errstr);
    }
    printf("回收子线程资源成功!\n");

    // 让主线程退出,当主线程退出时,不会影响其他正常运行的线程。
    pthread_exit(NULL);

    return 0;   // exit(0);
}

对于pthread_join的第二个参数,需要的是一个二级指针,接收子线程退出时的返回值,现在在子线程中定义一个value变量,然后返回它。

并且在主线程中接收这个值,并打印。

#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>

void * callback(void * arg) {
    printf("child thread id : %ld\n", pthread_self());
    //sleep(3);
    //return NULL;    // pthread_exit(NULL);

    int value = 10;
    pthread_exit((void *)&value);  //子线程中有返回值
} 

int main() {

    // 创建一个子线程
    pthread_t tid;
    int ret = pthread_create(&tid, NULL, callback, NULL);

    if(ret != 0) {
        char * errstr = strerror(ret);
        printf("error : %s\n", errstr);
    }

    // 主线程,先打印0-4
    for(int i = 0; i < 5; i++) {
        printf("%d\n", i);
    }

    //主线程打印子线程的id和主线程的id
    printf("tid : %ld, main thread id : %ld\n", tid ,pthread_self());


    //主线程调用pthread_join回收子线程的资源
    int * thread_retval;

    ret = pthread_join(tid, (void **)&thread_retval);
    if(ret != 0) {
        char * errstr = strerror(ret);
        printf("error : %s\n", errstr);
    }
 
    printf("the exit data : %d\n", *thread_retval);  //*thread_retval解引用
    printf("回收子线程资源成功!\n");

    // 让主线程退出,当主线程退出时,不会影响其他正常运行的线程。
    pthread_exit(NULL);

    return 0;   // exit(0);
}

按理来说,应该输出的是10,每次执行还都是不一样的值。其实在子线程中,value是一个局部变量,子线程拥有自己的栈空间(它所属的进程虚拟地址空间中栈空间的一部分),执行完毕后,栈空间被释放,因此输出的是一个随机值。

我们可以将value定义为全局变量,这样的话就可以成功打印输出:

int pthread_join(pthread_t thread, void **retval);第二个参数为什么是一个二级指针。

函数callback返回的是一个指针,要想接收这个返回值需要一个指针类型。

所以定义了 int *thread_retval去接收返回的指针。

但是如果直接将 thread_retval传入函数pthread_join,它就是一个局部参数,当结束函数pthread_join回到主线程,thread_retval的值是不会改变的。这样起不到用thread_retval接收返回值的作用。

要改变值,就要传递地址,所以得传&thread_retval,因为thread_retval是一个指针,而&是取地址,所以就成了**的参数类型。

就比如说,以下代码:

int data = 100;
void change(int a)
{
    a = 10;
}

在这个函数中,a是一个局部变量,无论在函数中怎么改变a的值,data的值是不会改变的。

为了能改变data的值,需要将形参改为int *a;

同理如果 data本身是一个指针,那么形参就应该为二级指针。

5.线程的分离pthread_detach

pthread_detach 是一个 POSIX 线程库中的函数,用于分离一个线程。当一个线程被分离时,它会在终止时自动释放其所有资源。如果不调用 pthread_detach 或 pthread_join,那么在主线程结束时,未分离的线程可能会变成僵尸线程,即它们已经终止但资源未被释放。

  1. 分离线程:当你调用 pthread_detach 并传递一个线程的线程ID时,这个线程就被标记为分离的。一旦这个线程终止,它的所有资源都会被自动释放,而不需要其他线程显式地调用 pthread_join

  2. 与pthread_join的区别pthread_join 是另一个函数,用于等待一个线程终止并回收其资源。与 pthread_detach 不同,如果你调用 pthread_join,那么调用线程(通常是主线程)会阻塞,直到指定的线程终止。在 pthread_join 返回后,你可以获取线程的返回值(如果有的话),并且该线程的资源会被自动释放。
  3. 只能选择一个:一个线程只能被分离一次,或者只能被另一个线程等待一次。一旦你选择了 pthread_detach 或 pthread_join,就不能再更改这个决策。
  4. 由于分离线程的资源会在其终止时自动释放,因此如果你需要线程的结果或需要在线程终止后继续访问其资源,那么你应该使用 pthread_join 而不是 pthread_detach

总之,pthread_detach 是一个非常有用的函数,它允许你避免在线程结束时显式地等待或管理线程的资源。但在使用它之前,你应该确保了解你的需求,并确定是否真的需要分离线程。

测试代码:

/*
    #include <pthread.h>
    int pthread_detach(pthread_t thread);
        - 功能:分离一个线程。被分离的线程在终止的时候,会自动释放资源返回给系统。不在需要其他线程通过join连接释放
          1.不能多次分离,会产生不可预料的行为。
          2.不能去连接(join)一个已经分离的线程,会报错。
        - 参数:需要分离的线程的ID
        - 返回值:
            成功:0
            失败:返回错误号
*/
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>

void * callback(void * arg) {
    printf("chid thread id : %ld\n", pthread_self());
    return NULL;
}

int main() {

    // 创建一个子线程
    pthread_t tid;

    int ret = pthread_create(&tid, NULL, callback, NULL);
    if(ret != 0) {
        char * errstr = strerror(ret);
        printf("error1 : %s\n", errstr);
    }

    // 输出主线程和子线程的id
    printf("tid : %ld, main thread id : %ld\n", tid, pthread_self());

    // 设置子线程分离,子线程分离后,子线程结束时对应的资源就不需要主线程释放
    ret = pthread_detach(tid);
    if(ret != 0) {
        char * errstr = strerror(ret);
        printf("error2 : %s\n", errstr);
    }

    // 设置分离后,对分离的子线程进行连接 pthread_join(),输出非法参数Invalid argument
    // ret = pthread_join(tid, NULL);
    // if(ret != 0) {
    //     char * errstr = strerror(ret);
    //     printf("error3 : %s\n", errstr);
    // }

    pthread_exit(NULL);  //主线程退出

    return 0;
}

问题:如果子线程先于pthread_detach()结束,那么子线程死了之后它的资源等待被回收,当执行pthread_detach()之后才进行回收吗?

子线程先于 pthread_detach() 调用结束,那么子线程会成为一个“僵尸线程”(zombie thread),它的资源不会被立即释放,而是等待 pthread_detach() 被调用。

在子线程结束和 pthread_detach() 被调用之间的这段时间里,子线程的资源(如栈内存)不会被操作系统自动回收。这意味着,如果你创建了大量的线程并且不及时地分离它们,可能会导致资源泄漏,因为每个已终止但尚未被分离的线程都会占用一些系统资源。

一旦 pthread_detach() 被调用,操作系统会知道这个线程已经终止,并且它的资源可以被安全地回收了。此时,操作系统会释放线程占用的所有资源,包括栈内存和线程控制块等。

如果你既没有调用 pthread_detach(),也没有调用 pthread_join(),那么当主线程结束时,所有未分离的线程都会被自动分离,并且它们的资源会被释放。然而,这种自动分离的行为通常不推荐使用,因为它可能会导致难以追踪的资源管理问题。最好显式地管理你的线程,决定何时分离它们,以及如何处理它们的返回值和资源。

线程分离不代表结束,pthread_detach函数是非阻塞的,无论先调用还是后调用该函数,子进程一样能够继续正常运行,只是能够让子线程在结束时无需pthread_join回收资源。

6.线程的取消pthread_cancle

pthread_cancel 是 POSIX 线程库中的一个函数,用于请求取消一个线程。当调用 pthread_cancel 并传递一个线程的线程ID时,你正在请求该线程在某个时刻终止其执行。然而,线程的取消是一个协作过程,这意味着线程本身需要配合取消请求的执行。

1. 请求取消:当你调用 pthread_cancel 时,你实际上是在向目标线程发送一个取消请求。这不会立即终止线程,而是设置了一个标志,表明线程应该在某个合适的时机终止。

2.协作式取消:线程取消是协作式的,这意味着线程需要定期检查取消请求,并在适当的时候响应它。线程可以通过调用 pthread_setcancelstate 和 pthread_setcanceltype 来控制其行为。默认情况下,线程会忽略取消请求,直到它执行某些取消点(cancellation points)的操作,如某些阻塞的系统调用时,执行线程的取消。 

关于取消点,可以通过man pthreads产看

3.取消状态和类型:

  • pthread_setcancelstate 用于设置线程的取消状态,可以是 PTHREAD_CANCEL_ENABLE(允许取消)或 PTHREAD_CANCEL_DISABLE(禁止取消)。
  • pthread_setcanceltype 用于设置线程的取消类型,可以是 PTHREAD_CANCEL_DEFERRED(延迟取消)或 PTHREAD_CANCEL_ASYNCHRONOUS(异步取消)。延迟取消是默认行为,它要求线程在取消点响应取消请求;异步取消则允许线程在任何时刻被取消。

4. 取消结果:被取消的线程可以通过调用 pthread_testcancel 来检查是否有取消请求挂起,并在适当的时候处理它。此外,线程可以通过 pthread_cancel 的返回值来确定它是否已经被取消。

5. 清理处理:当线程被取消时,它可以注册一个或多个取消处理函数(通过 pthread_cleanup_push 和 pthread_cleanup_pop),这些函数会在线程被取消时自动执行,用于释放资源或执行其他必要的清理工作。

6. 注意事项:由于线程取消是协作式的,因此它并不总是能立即终止线程。此外,取消一个线程可能会导致复杂的状态问题,特别是如果线程正在访问共享资源或执行关键区段代码。因此,在使用 pthread_cancel 时需要特别小心,并确保线程能够安全地响应取消请求。

如果子线程中没有取消点的话,子线程是不是一直运行到结束?---是

printf也是取消点吗?---是 printf 会调用 write在stdout里面写数据,而write是要进行一个用户态到内核态的切换的,printf内部调用了取消点函数。

测试代码:

/*
    #include <pthread.h>
    int pthread_cancel(pthread_t thread);
        - 功能:取消线程(让线程终止)
            取消某个线程,可以终止某个线程的运行,(杀毒软件,杀毒的时候不想杀毒了,就可以点击取消,这就是取消某个线程)
            但是并不是立马终止,而是当子线程执行到一个取消点,线程才会终止。
            取消点:系统规定好的一些系统调用,我们可以粗略的理解为从用户区到内核区的切换,这个位置称之为取消点。
*/

#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>

void * callback(void * arg) {
    printf("chid thread id : %ld\n", pthread_self());
    for(int i = 0; i < 10; i++) {
        printf("child : %d\n", i);
    }
    return NULL;
}

int main() {
    
    // 创建一个子线程
    pthread_t tid;

    int ret = pthread_create(&tid, NULL, callback, NULL);
    if(ret != 0) {
        char * errstr = strerror(ret);
        printf("error1 : %s\n", errstr);
    }

    // 取消线程
    pthread_cancel(tid);

    for(int i = 0; i < 5; i++) {
        printf("%d\n", i);
    }

    // 输出主线程和子线程的id
    printf("tid : %ld, main thread id : %ld\n", tid, pthread_self());

    
    pthread_exit(NULL);

    return 0;
}

7.线程属性pthread_attr_t

pthread_attr_t 是 POSIX 线程库中用于描述线程属性的数据类型。线程属性是一组与线程相关的参数,它们影响线程的创建和运行方式。通过修改线程属性,可以控制线程的各种行为,如堆栈大小、优先级、分离状态等。

线程属性通常通过 pthread_attr_init 函数进行初始化,然后通过一系列设置函数(如 pthread_attr_setstacksizepthread_attr_setdetachstate 等)来配置具体的属性。一旦线程属性被配置好,它们就可以传递给 pthread_create 函数,用于创建具有特定属性的线程。

以下是一些常用的线程属性及其含义:

  1. 分离状态(Detach State)
    • 通过 pthread_attr_setdetachstate 设置。
    • 决定了线程终止时其行为。如果设置为 PTHREAD_CREATE_DETACHED,则线程在终止时会自动释放其资源,无需其他线程调用 pthread_join。如果设置为 PTHREAD_CREATE_JOINABLE(默认值),则线程终止时不会释放资源,需要其他线程调用 pthread_join 来回收资源。
  2. 堆栈大小(Stack Size)
    • 通过 pthread_attr_setstacksize 设置。
    • 定义了线程堆栈的大小。如果未设置或设置为 0,则使用默认堆栈大小。
    • 一般不需要自己来管理线程堆栈,Linux默认为每个线程分配了足够的堆栈空间(一般是8MB)8 MB = 8388608 B,可以用ulimit -s 命令查看或修改这个默认值;
  3. 优先级(Priority)
    • 通过 pthread_attr_setschedparam 设置。
    • 决定了线程的调度优先级。不同的调度策略可能会有不同的优先级范围。
  4. 调度策略(Scheduling Policy)
    • 同样通过 pthread_attr_setschedparam 设置。
    • 定义了线程的调度方式,如 FIFO、ROUND_ROBIN 或其他系统支持的调度策略。
  5. 守护线程(Daemon Thread)
    • 通过 pthread_attr_setdaemon 设置。
    • 如果一个线程被设置为守护线程,那么当主线程结束时,守护线程也会自动退出,无论其是否已经完成执行。
  6. 堆栈地址(Stack Address)
    • 通过 pthread_attr_setstackaddr 设置。
    • 允许指定线程堆栈的起始地址。通常情况下,不需要手动设置这个属性,除非有特殊的内存布局要求。

使用线程属性时,需要注意以下几点:

  • 在调用 pthread_create 之前,必须初始化线程属性对象,并且在使用完属性对象后,应调用 pthread_attr_destroy 来释放相关资源。
  • 某些属性可能在不同的系统或不同的线程库实现中有所不同或不受支持。因此,在编写跨平台的代码时,需要特别注意属性的兼容性和可移植性。
  • 线程属性是线程创建时的一次性设置,一旦线程创建成功,其属性就不能再被修改。如果需要修改线程的行为,通常需要创建一个新的线程并配置新的属性。

测试代码:

/*
    int pthread_attr_init(pthread_attr_t *attr);
        - 初始化线程属性变量

    int pthread_attr_destroy(pthread_attr_t *attr);
        - 释放线程属性的资源

    int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
        - 获取线程分离的状态属性

    int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
        - 设置线程分离的状态属性
*/     

#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>

void * callback(void * arg) {
    printf("chid thread id : %ld\n", pthread_self());
    return NULL;
}

int main() {

    // 创建一个线程属性变量
    pthread_attr_t attr;
    // 初始化属性变量
    pthread_attr_init(&attr);

    // 设置分离状态属性 PTHREAD_CREATE_DETACHED则线程在终止时会自动释放其资源,无需其他线程调用 pthread_join
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

    // 创建一个子线程
    pthread_t tid;

    int ret = pthread_create(&tid, &attr, callback, NULL);
    if(ret != 0) {
        char * errstr = strerror(ret);
        printf("error1 : %s\n", errstr);
    }

    // 获取线程的栈的大小,返回的是字节数
    size_t size;
    pthread_attr_getstacksize(&attr, &size);
    printf("thread stack size : %ld\n", size);

    // 输出主线程和子线程的id
    printf("tid : %ld, main thread id : %ld\n", tid, pthread_self());

    // 释放线程属性资源
    pthread_attr_destroy(&attr);

    pthread_exit(NULL);

    return 0;
}

  • 24
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值