Unix C语言POSIX的线程创建、获取线程ID、汇合线程、分离线程、终止线程、线程的比较

23 篇文章 3 订阅

创建线程


POSIX标准线程接口需要引入<pthread.h>头文件。
如果通过gcc进行编辑的话,需要连接pthread库:gcc ... -lpthread

创建线程函数概述

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);

  • pthread_t *thread :该参数是POSIX定义的线程id,用于创建线程后获取线程id。
  • const pthread_attr_t *attr:线程有很多属性用来设置该线程的一些行为与特性,通过该参数用来定义。当传入NULL则使用缺省设置。通常没有特别要求,使用缺省即可。
  • void *(*start_routine)(void *):一个函数指针。当一个新的线程被创建后,内核会调用该函数进行线程过程的执行。
  • void *arg:需要传入线程过程函数的入参,由内核在调用过程函数时传入。
创建线程代码示例
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

/**
 * 函数执行体。类似java中的Thread#run函数。
 * 当函数体执行完毕,则线程结束。
 * @param arg 创建线程时传入的参数。
 * @return 线程返回的结果,可以被创建该线程的父线程通过pthread_join获取。
 */
void *thread_proc(void *arg) {
    for (int i = 0; i < 100; ++i) {
        usleep(1000 * 5);
        printf("%lld", (long long) arg); //打印传入的数字
    }
    return NULL;
}


int main(void) {
    setbuf(stdout, NULL);
    for (long long i = 1; i <= 3; ++i) {//创建多个线程
        pthread_t tid; //线程id
        int errno = pthread_create(&tid, NULL, thread_proc, (void *) i); //创建线程,并传入一个数字
        if (errno) { //当线程创建失败,返回的errno非0
            fprintf(stderr, "pthread_create:%s\n", strerror(errno)); //打印异常信息
            return -1;
        }
    }
    getchar();//防止主线程退出,回车即退出。
    return 0;
}

运行可发现多个线程同时打印数字。

获取线程ID


获取线程id函数概述

pthread_t pthread_self(void);
返回当前正在调用的线程id。
需要注意的是,返回的pthread_t是POSIX定义的线程id,并不是实际系统内核的线程ID。如果需要获取系统内核的线程ID,则需要引入<sys/syscall.h>头文件,并通过系统调用syscall(SYS_gettid)来获取。

获取线程id代码示例
#include <pthread.h>
#include <stdio.h>
#include <string.h>

/**
 * 函数执行体。类似java中的Thread#run函数。
 * 当函数体执行完毕,则线程结束。
 * @param arg 创建线程时传入的参数。
 * @return 线程返回的结果,可以被创建该线程的父线程通过pthread_join获取。
 */
void *thread_proc(void *arg) {
    pthread_t pt = pthread_self();//获取当前线程id
    printf("%lu线程:线程过程函数被调用\n", (unsigned long) pt);
    return NULL;
}


int main(void) {
    pthread_t tid; //线程id
    int errno = pthread_create(&tid, NULL, thread_proc, NULL); //创建线程
    if (errno) { //当线程创建失败,返回的errno非0
        fprintf(stderr, "pthread_create:%s\n", strerror(errno)); //打印异常信息
        return -1;
    }
    printf("%lu线程:创建了子线程%lu\n", (unsigned long) pthread_self(), (unsigned long) tid);
    getchar();
    return 0;
}

汇合线程


汇合线程场景:

  • 同步子线程,等待子线程结束
  • 获取线程过程函数的返回值
  • 清理并释放子线程的残余资源。(有点类似僵尸进程,线程结束后,依然会有返回值和线程id没有被释放,所以线程结束后需要回收。"分离线程"会自动回收。)

所谓汇合线程,实质是父线程等待子线程结束并获取子线程返回的结果的操作。
常用于需要提高程序性能而通过创建子线程来并发执行某些操作,然后父线程获取操作结果的场景。
类似java中的Thread#join()函数。

汇合线程函数概述

int pthread_join(pthread_t thread, void **value_ptr);

  • pthread_t thread:线程id。
  • void **value_ptr :用于获取线程返回值的指针。

该函数如果被调用,除非目标线程过程函数已终止,否则将暂停调用线程的执行,直到目标线程终止。

汇合线程代码示例
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

/**
 * 函数执行体。类似java中的Thread#run函数。
 * 当函数体执行完毕,则线程结束。
 * @param arg 创建线程时传入的参数。
 * @return 线程返回的结果,可以被创建该线程的父线程通过pthread_join获取。
 */
void *thread_proc(void *arg) {
    for (int i = 0; i < 100; ++i) {
        usleep(1000 * 5);
        printf("%lld", (long long) arg);
    }
    return arg;
}


int main(void) {
    setbuf(stdout, NULL);
    for (long long i = 1; i <= 3; ++i) {//创建多个线程
        pthread_t tid; //线程id
        int errno = pthread_create(&tid, NULL, thread_proc, (void *) i); //创建线程,并传入一个数字
        if (errno) { //当线程创建失败,返回的errno非0
            fprintf(stderr, "pthread_create:%s\n", strerror(errno)); //打印异常信息
            return -1;
        }
        void *val;
        errno = pthread_join(tid, &val); //汇合线程,等待传入tid对应的线程执行结束
        if (errno) { //当线程汇合失败,返回的errno非0
            fprintf(stderr, "pthread_join:%s\n", strerror(errno)); //打印异常信息
            return -1;
        }
        printf("线程%lu返回值:%lld\n", (unsigned long) tid, (long long) val);
    }
    getchar();//防止主线程退出,回车即退出。
    return 0;
}

分离线程


如果一个线程被设置为分离线程,那么该线程不能够也不需要被汇合,它终止后所参与的资源会被系统自动回收。因此在不需要等待线程结束,并且不关心线程过程函数返回值的场景,使用分离线程是最好的方式。
分离线程不允许被汇合,也就意味着,不能通过pthread_join来试图等待一个分离线程返回。

第一种方式:后标记分离线程

int pthread_detach(pthread_t thread);

该方式,先通过pthread_create创建一个线程,然后通过pthread_detach函数来设置该线程为分离线程。如果创建时采用的缺省属性,则默认为可汇合的线程。可汇合线程就意味着可以通过pthread_join函数来调用,并获取返回值。由于线程过程函数什么时间执行完毕是不确定的,所以有可能在调用pthread_detach函数时,线程已经结束了,这就没有任何意义了,所以建议采用下文说的第二种方式:创建即分离线程。

代码示例:
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

/**
 * 函数执行体。类似java中的Thread#run函数。
 * 当函数体执行完毕,则线程结束。
 * @param arg 创建线程时传入的参数。
 * @return 线程返回的结果,可以被创建该线程的父线程通过pthread_join获取。
 */
void *thread_proc(void *arg) {
    for (int i = 0; i < 100; ++i) {
        usleep(1000 * 5);
        printf("%lld", (long long) arg);
    }
    return arg;
}


int main(void) {
    setbuf(stdout, NULL);
    for (long long i = 1; i <= 3; ++i) {//创建多个线程
        pthread_t tid; //线程id
        int errno = pthread_create(&tid, NULL, thread_proc, (void *) i); //创建线程,并传入一个数字
        if (errno) { //当线程创建失败,返回的errno非0
            fprintf(stderr, "pthread_create:%s\n", strerror(errno)); //打印异常信息
            return -1;
        }
        errno = pthread_detach(tid);//**分离线程**
        if (errno) { //当线程分离失败,返回的errno非0
            fprintf(stderr, "pthread_detach:%s\n", strerror(errno)); //打印异常信息
            return -1;
        }
    }
    printf("主线程执行完毕。\n");
    getchar();//防止主线程退出,回车即退出。
    return 0;
}
第二种方式:创建即分离线程

该方式,在通过pthread_create创建线程时,设置其线程属性,使其创建线程即为分离线程。该方式避免了通过pthread_detach函数设置的不确定性。

pthread_create函数的第二个参数为线程属性,当传入NULL时,则采用缺省属性。要创建分离线程,需要定义线程属性并传入:

  1. pthread_attr_t attr; 定义线程属性变量。
  2. pthread_attr_init(&attr); 初始化线程属性,设置缺省值。
  3. pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED); 设置分离线程状态。
  4. pthread_attr_destroy(&attr); 销毁线程属性中的动态资源。属性用完后,要销毁,避免内存泄漏。
代码示例
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

/**
 * 函数执行体。类似java中的Thread#run函数。
 * 当函数体执行完毕,则线程结束。
 * @param arg 创建线程时传入的参数。
 * @return 线程返回的结果,可以被创建该线程的父线程通过pthread_join获取。
 */
void *thread_proc(void *arg) {
    for (int i = 0; i < 100; ++i) {
        usleep(1000 * 5);
        printf("%lld", (long long) arg);
    }
    return arg;
}


int main(void) {
    setbuf(stdout, NULL);
    pthread_t tid; //线程id
    pthread_attr_t attr; //定义线程属性变量
    pthread_attr_init(&attr); //初始化线程属性
    pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED); //设置分离线程状态
    for (long long i = 1; i <= 3; ++i) {//创建多个线程
        int errno = pthread_create(&tid, &attr, thread_proc, (void *) i); //创建线程,并传入一个数字
        if (errno) { //当线程创建失败,返回的errno非0
            fprintf(stderr, "pthread_create:%s\n", strerror(errno)); //打印异常信息
            return -1;
        }
    }
    pthread_attr_destroy(&attr);//销毁线程属性中的动态资源
    printf("主线程执行完毕。\n");
    getchar();//防止主线程退出,回车即退出。
    return 0;
}

终止线程


当线程过程函数执行完毕,线程即终止。
当线程过程函数在内部的调用逻辑过程中,需要再任意地方需要结束当前线程,可调用pthread_exit函数来结束。
void pthread_exit(void *value_ptr);
传入的void *value_ptr与线程return的值是一个意思,外部通过pthread_join获取的线程值,如果通过pthread_exit返回的,即该值;如果通过return返回的,即return的值。

代码示例

thread_proc不再return,直接通过pthread_exit返回。

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

/**
 * 函数执行体。类似java中的Thread#run函数。
 * 当函数体执行完毕,则线程结束。
 * @param arg 创建线程时传入的参数。
 * @return 线程返回的结果,可以被创建该线程的父线程通过pthread_join获取。
 */
void *thread_proc(void *arg) {
    for (int i = 0; i < 100; ++i) {
        usleep(1000 * 5);
        printf("%lld", (long long) arg);
    }
    pthread_exit(arg);//结束线程
//    return arg;
}


int main(void) {
    setbuf(stdout, NULL);
    for (long long i = 1; i <= 3; ++i) {//创建多个线程
        pthread_t tid; //线程id
        int errno = pthread_create(&tid, NULL, thread_proc, (void *) i); //创建线程,并传入一个数字
        if (errno) { //当线程创建失败,返回的errno非0
            fprintf(stderr, "pthread_create:%s\n", strerror(errno)); //打印异常信息
            return -1;
        }
        void *val;
        errno = pthread_join(tid, &val); //汇合线程,等待传入tid对应的线程执行结束
        if (errno) { //当线程汇合失败,返回的errno非0
            fprintf(stderr, "pthread_join:%s\n", strerror(errno)); //打印异常信息
            return -1;
        }
        printf("线程%lu返回值:%lld\n", (unsigned long) tid, (long long) val);
    }
    getchar();//防止主线程退出,回车即退出。
    return 0;
}

线程的比较


关于线程的比较有一个点需要说明的是,POSIX标准提供的线程ID为pthread_t类型的结构体,不是内核系统本身的线程ID,我们一般建议使用POSIX的API,来让我们写的代码具有系统移植性。
那么当我们需要通过pthread_self函数返回的id与某个pthread_t来比较时,也是如此。POSIX提供了一个pthread_equal函数来做两个线程id之间的比较。

为何采用pthread_equal而不直接采用==进行比较,主要也是考虑到pthread_t可能在不同的操作系统,结构体不一样,所以采用pthread_equal就可以屏蔽掉操作系统不同带来的问题。

int pthread_equal(pthread_t t1, pthread_t t2)
当t1与t2相等时,返回非0;不相等时,返回0。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值