【Linux线程】二、线程控制原语

在这里插入图片描述

欢迎关注博主 Mindtechnist 或加入【Linux C/C++/Python社区】一起探讨和分享Linux C/C++/Python/Shell编程、机器人技术、机器学习、机器视觉、嵌入式AI相关领域的知识和技术。



专栏传送门 :《Linux从小白到大神》 | 系统学习Linux开发、VIM/GCC/GDB/Make工具、Linux文件IO、进程管理、进程通信、多线程等,请关注专栏免费学习。


因为线程都是库函数,所以编译的时候都要加一个-pthread选项,表示包含线程库,总之,编译线程相关的程序都要在gcc后面加参数 -pthread。Compile and link with -pthread. (有的时候是加-lpthread)

1. pthread_self函数

  • 头文件及函数原型
#include <pthread.h>

pthread_t pthread_self(void);     
  • 函数描述

    The pthread_self() function returns the ID of the calling thread. 获得线程的pid,类似于进程中的getpid()。

  • 函数参数

    void

  • 函数返回值

    This function always succeeds, returning the calling thread’s ID.

2. pthread_create函数

  • 头文件及函数原型
#include <pthread.h>

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
  • 函数描述

    The pthread_create() function starts a new thread in the calling process. 创建一个线程。

  • 函数参数

    • thread:它是一个传出参数,代表线程ID,一级指针做输出。
    • attr:代表线程属性
    • start_routine:回调函数
    • arg:线程执行回调函数时的参数
  • 函数返回值

    On success, pthread_create() returns 0; on error, it returns an error number, and the contents of *thread are undefined.

示例:创建一个线程

/************************************************************
  >File Name  : pth_test.c
  >Author     : Mindtechnist
  >Company    : Mindtechnist
  >Create Time: 2022年05月25日 星期三 17时17分15秒
************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

void* th_print(void* arg)
{
    printf("th_print thread: %s, pid: %d, tid: %lu\n", (char*)arg, getpid(), pthread_self());
    return NULL;
}

int main(int argc, char* argv[])
{
    pthread_t tid;
    pthread_create(&tid, NULL, th_print, (void*)argv[1]);
    printf("main thread, pid: %d, tid: %lu\n", getpid(), pthread_self());
    sleep(1); /*不睡眠的话,进程退出了,线程还没结束就没了*/
    return 0;
}

可以看到进程ID是一样的,线程ID不同。

在这里插入图片描述

3. pthread_exit函数

  • 头文件及函数原型
#include <pthread.h>

void pthread_exit(void *retval);
  • 函数描述

    The pthread_exit() function terminates the calling thread and returns a value via retval that (if the thread is joinable) is available to another thread in the same process that calls pthread_join(3). 退出一个线程,注意是退出线程。

  • 函数参数

    • retval:退出值
  • 函数返回值

    This function does not return to the caller.

示例:线程的三种退出方式

/************************************************************
  >File Name  : pth_test2.c
  >Author     : Mindtechnist
  >Company    : Mindtechnist
  >Create Time: 2022年05月25日 星期三 17时17分15秒
************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

void* th_print(void* arg)
{
    printf("th_print thread: %s, pid: %d, tid: %lu\n", (char*)arg, getpid(), pthread_self());
    /*return NULL; */
    pthread_exit(NULL);
    /*exit(1); */
    /*三种退出方式是不一样的,前两个都是正常退出,只退出线程。
    线程中return表示退出线程,主线程中return代表退出进程。
    pthread_exit表示退出线程。
    而exit()是退出整个进程,所以在线程中应该使用前两种*/
}

int main(int argc, char* argv[])
{
    pthread_t tid;
    pthread_create(&tid, NULL, th_print, (void*)argv[1]);
    printf("main thread, pid: %d, tid: %lu\n", getpid(), pthread_self());
    sleep(5);
    printf("sleep end...\n");
    pthread_exit(NULL);
    return 0;
}

4. pthread_join函数

  • 头文件及函数原型
#include <pthread.h>

int pthread_join(pthread_t thread, void **retval);
  • 函数描述

    The pthread_join() function waits for the thread specified by thread to terminate. 线程回收函数,会阻塞等待线程结束。

  • 函数参数

    • thread:线程ID,pthread_create传出的第一个参数。
    • retval:二级指针做输出,传出线程退出的信息。
  • 函数返回值

    On success, pthread_join() returns 0; on error, it returns an error number.

示例:线程回收

/************************************************************
  >File Name  : join_test.c
  >Author     : Mindtechnist
  >Company    : Mindtechnist
  >Create Time: 2022年05月25日 星期三 17时17分15秒
************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

void* th_print(void* arg)
{
    printf("th_print thread: %s, pid: %d, tid: %lu\n", (char*)arg, getpid(), pthread_self());
    sleep(3);
    printf("th_print thread end\n");
    return (void*)101;
    /*pthread_exit((void*)101); 等效*/
}

int main(int argc, char* argv[])
{
    pthread_t tid;
    pthread_create(&tid, NULL, th_print, (void*)argv[1]);
    printf("main thread, pid: %d, tid: %lu\n", getpid(), pthread_self());
    void* ret;
    pthread_join(tid, &ret); /*线程回收函数是阻塞等待的
    		只有等到线程结束了,才会执行下面的打印语句*/
    printf("th_print thread return: %d\n", (int)ret);
    return 0;
}

编译执行,可以看到线程退出信息被返回。

在这里插入图片描述

5. pthread_detach函数与线程分离

5.1 pthread_detach函数

  • 头文件及函数原型
#include <pthread.h>

int pthread_detach(pthread_t thread);
  • 函数描述

    The pthread_detach() function marks the thread identified by thread as detached. 实现线程分离。

  • 函数参数

    • thread:线程ID
  • 函数返回值

    On success, pthread_detach() returns 0; on error, it returns an error number.

5.2 什么是线程分离

线程分离状态是指:指定该状态,那么线程将主动与主控线程断开关系。等到线程结束后,其退出状态不由其它线程获取,而是直接自己自动释放,多用于网络、多线程服务器等场合。设置线程分离既可以使用pthread_detach()函数,也可以使用pthread_create()函数参数attr来设置线程分离属性。

如果进程有线程分离机制,那么就不会再产生僵尸进程。僵尸进程的产生主要是由于进程终止后,大部分资源被释放,但是仍然有残留西元存在于系统中,导致内核认为该进程仍然存在。

一般情况下,线程终止后,其终止状态一直保留到其他线程调用pthread_join()获取它的状态为止。但是线程也可以被置为detach状态,这样的线程一旦终止就立刻回收它占用的所有资源,而不保留终止状态。不能对一个已经处于detach状态的线程调用pthread_join(),因为这样调用会返回EINVAL错误。也就是说,如果一个线程已经调用了pthread_join()就不能再调用pthread_join()函数了。

/************************************************************
  >File Name  : detach_test.c
  >Author     : Mindtechnist
  >Company    : Mindtechnist
  >Create Time: 2022年05月25日 星期三 17时17分15秒
************************************************************/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>

void* th_print(void* arg)
{
    printf("th_print thread: %s, pid: %d, tid: %lu\n", (char*)arg, getpid(), pthread_self());
    sleep(3);
    printf("th_print thread end\n");
    return (void*)101;
    /*pthread_exit((void*)101); 等效*/
}

int main(int argc, char* argv[])
{
    pthread_t tid;
    pthread_create(&tid, NULL, th_print, (void*)argv[1]);
    printf("main thread, pid: %d, tid: %lu\n", getpid(), pthread_self());
    pthread_detach(tid);
    sleep(6);
    int ret = 0;
    if((ret = pthread_join(tid, NULL)) > 0)
    {
        printf("pthread_join errno: %d, %s\n", ret, strerror(ret));
    }
    return 0;
}

编译运行,可以看到pthread_join报错。

在这里插入图片描述

5.3 线程的分离状态

线程的分离状态决定一个线程以什么样的方式来终止自己。

  • 非分离状态:线程的默认属性是非分离状态,在这种情况下,原有的线程等待创建的线程结束。只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。
  • 分离状态:分离线程没有被其他线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。

设置分离状态的函数

  • 进程属性控制
#include <pthread.h>

int pthread_attr_init(pthread_attr_t *attr); /*初始化属性*/
int pthread_attr_destroy(pthread_attr_t *attr); /*销毁属性*/
  • 设置属性分离态
#include <pthread.h>

int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);

/*
PTHREAD_CREATE_DETACHED	设置分离
PTHREAD_CREATE_JOINABLE	需要pthread_join回收
*/

示例:设置分离状态属性

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

void* th_print(void* arg)
{
    printf("th_print...\n");
    return NULL;
}

int main()
{
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    pthread_t tid;
    pthread_create(&tid, &attr, th_print, NULL);
    int ret;
    if((ret = pthread_join(tid, NULL)) > 0)
    {
        printf("pthread_join errno: %d, %s\n", ret, strerror(ret));
    }
    pthread_attr_destroy(&attr);
    return 0;
}

需要注意的是,如果设置一个线程为分离状态,而这个线程运行又非常快,它很可能在pthread_create函数返回之前就终止了,它终止后就可能将线程号和系统资源移交给其它线程使用,这样调用pthread_create的线程就得到了错误的线程号。要避免这种情况的话可以采取一定的同步措施。最简单的方法之一就是在被创建的线程里调用pthread_cond_timedwait函数,让线程多等待一会,留出足够的时间让函数pthread_create返回。但是不能使用wait函数,因为它是让整个进程睡眠,无法达到线程同步的目的。

6. pthread_cancel函数与线程取消点

6.1 pthread_cancel函数

  • 头文件及函数原型
#include <pthread.h>

int pthread_cancel(pthread_t thread);
  • 函数描述

    The pthread_cancel() function sends a cancellation request to the thread thread. 取消线程,被取消的线程退出值定义在Linux的pthread库中,常数PTHREAD_CANCELED的值是-1,在头文件pthread.h中它的定义为 #define PTHREAD_CANCELED ((void*)-1),也就是说,使用pthread_join回收被取消的线程时,得到的返回值是-1。

  • 函数参数

    • thread:线程ID,pthread_create传出的第一个参数。
  • 函数返回值

    On success, pthread_cancel() returns 0; on error, it returns a non-zero error number.

/************************************************************
  >File Name  : join_test.c
  >Author     : Mindtechnist
  >Company    : Mindtechnist
  >Create Time: 2022年05月25日 星期三 17时17分15秒
************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

void* th_print(void* arg)
{
    while(1)
    {
        printf("th_print thread: %s, pid: %d, tid: %lu\n", (char*)arg, getpid(), pthread_self());
    	sleep(1);
    }
    pthread_exit((void*)101); 
}

int main(int argc, char* argv[])
{
    pthread_t tid;
    pthread_create(&tid, NULL, th_print, (void*)argv[1]);
    printf("main thread, pid: %d, tid: %lu\n", getpid(), pthread_self());
    sleep(3);
    pthread_cancel(tid); /*杀死线程*/
    void* ret;
    pthread_join(tid, &ret); 
    printf("th_print thread return: %d\n", (int)ret);
    return 0;
}

杀死线程的返回值是-1

在这里插入图片描述

6.2 什么是取消点

需要注意的是,线程取消并不是实时的,而是具有一定的延时,需要等待线程达到某个取消点(检查点)。就比如说我们停车,必须到达停车位才能停车,杀死线程的时候也并不是立马就杀死,而是等到达取消点了才能杀死。比如说在上面的程序中,如果我们th_print()函数改造为一个空虚循环,这个先杀就无法使用pthread_cancel()杀死了,因为没有取消点。

void* th_print(void* arg)
{
    while(1)
    {
       ;
    }
    pthread_exit((void*)101); 
}

取消点就是线程检查是否被取消,并按照请求进行动作的一个位置。通常是一些系统调用creat、open、pause、close、read、write等,通过 man 7 pthreads 可以查看具备这些取消点的系统调用列表。可以认为,一个系统调用(陷入内核)就是一个取消点。如果没有取消点的话,可以通过调用 pthread_testcansel() 函数自行设置一个取消点。被取消的线程退出值定义在Linux的pthread库中,常数PTHREAD_CANCELED的值是-1,在头文件pthread.h中它的定义为 #define PTHREAD_CANCELED ((void*)-1),也就是说,使用pthread_join回收被取消的线程时,得到的返回值是-1。

7. pthread_equal函数

  • 头文件及函数原型
#include <pthread.h>

int pthread_equal(pthread_t t1, pthread_t t2);
  • 函数描述

    The pthread_equal() function compares two thread identifiers. 比较两个线程ID是否相等。官方推荐使用函数判断两个线程ID是否相等,而不是直接通过tip1=tid2来判断,因为未来Linux线程ID的类型pthread_t可能被修改为结构体实现。另外要注意的是,在一个进程内,线程ID可以唯一确定一个线程,但是在整个操作系统内就不可以了。而进程的PID在操作系统内是唯一的。

  • 函数参数

    • t1:线程1ID
    • t2:线程2ID
  • 函数返回值

    If the two thread IDs are equal, pthread_equal() returns a non-zero value; otherwise, it returns 0.

8. 线程使用注意事项

8.1 NPTL

  • NPTL就是 Native POSIX Thread Library;

  • 查看当前pthread库版本命令

    getconf GNU_LIBPTHREAD_VERSION
    
  • 使用线程库时需要gcc指定 -pthread 或 -lpthread

8.2 线程使用注意事项

  • 要想主线程退出其他线程不退出,主线程应调用pthread_exit。

  • 避免僵尸进程

    • 回收线程:pthread_join
    • 分离线程
      • pthread_detach
      • pthread_create指定分离属性
  • malloc和mmap申请的内存可以在其他线程中释放。

  • 应避免在多线程模型中调用fork,除非马上exec,子进程中只有调用fork的线程存在,其它线程在子进程中都pthread_exit。

  • 多线程中避免使用信号。

  • 线程创建的个数一般是:CPU核数*2+2

附:多线程创建

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

void th_func(void* num) /*不稳定*/
{
    int ret = (int)num;
    printf("thread %d, tid: %d\n", num, pthread_self());
    return (void*)(ret+10);
}

int main(int argc, char* argv[])
{
    pthread_t tid[5];
    int i;
    for(i = 0; i < 5; i++)
    {
        pthread_create(&tid[i], NULL, th_func, (void*)i); /*传地址不可以*/
    }
    for(i = 0; i < 5; i++)
    {
        void* ret;
        pthread_join(tid[i], &ret);
        printf("thread %d, ret: %d\n", i, (int)ret);
    }
    return 0;
}

在这里插入图片描述
在这里插入图片描述


posted @ 2022-09-22 12:13  Mindtechnist  阅读(13)  评论(0编辑  收藏  举报  来源