• Linux线程控制


    Linux支持POSIX多线程接口,称为pthread(POSIX Thread的简称),编写Linux下的多线程应用程序,头文件需要包pthread.h

    一、线程创建

    函数名称pthread_create
    函数功能创建一个线程
    头文件#include
    函数原型int pthread_create( pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
    参数thread:指向线程标识符的指针,用来存储线程标识符
    attr:设置线程属性,NULL表示使用默认属性
    start_routine:是个函数地址,线程启动后要执行的函数
    arg:传给线程启动函数的参数
    返回值成功返回0,失败返回出错编号
    创建成功时,由thread指向的内存单元被设置为新创建的线程的ID

    pthread.h中还声明了一个pthread_self函数,用于获取当前线程自身的ID

    函数名称pthread_self
    函数功能获取自身线程ID
    头文件pthread
    函数原型pthread_t pthread_self(void);
    参数
    返回值>0:返回调用线程的ID
    void*thread_run(void*args)
    {
        while(1)
        {
            printf("我是新线程[%s],我创建的线程的ID是:%lu\n",(const char*)args,pthread_self());
            sleep(1);
        }
    }
    int main()
    {
        pthread_t tid;
        pthread_create(&tid,NULL,thread_run,"new thread");
    
        while(1)
        {
            printf("我是主线程,我创建的线程的ID是:%lu\n",tid);
            sleep(1);
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    结果展示:
    我们可以看到主线程和新线程的ID是一样的,而且是很大的值。
    在这里插入图片描述
    我们再来观察一下LWP,为什么和线程ID不一样呢?

    注意:pthread并不是Linux系统默认的库,而是POSIX线程库。在Linux中将其作为一个库来使用,因此编译链接时需要加上-lpthread以显式链接该库

    如下创建多个线程

    //创建多个线程
    void *thread_run(void *args)
    {
        while (1)
        {
            //printf("我是新线程[%s],我创建的线程的ID是:%lu\n", (const char *)args, pthread_self());
            sleep(3);
        }
    }
    int main()
    {
        pthread_t tid[5];
        int i = 0;
        for (i = 0; i < 5; i++)
        {
            pthread_create(tid + i, NULL, thread_run,(void*)"new thread");
        }
        while (1)
        {
            printf("我是主线程,我的thread ID是:%lu\n", pthread_self());
            printf("##############begin#################\n");
            int i = 0;
            for (i = 0; i < 5; i++)
            {
                printf("我创建的线程[%d]是:%lu\n", i, tid[i]);
            }
            printf("##############end###################\n");
            sleep(1);
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    结果展示:
    在这里插入图片描述
    线程的健壮性不强

    //创建多个线程,其中让3号线程出现野指针
    void *thread_run(void *args)
    {
        int num=*(int*)args;
        while (1)
        {
            printf("我是新线程[%d],我创建的线程的ID是:%lu\n", num, pthread_self());
            sleep(10);
    
            // 野指针问题
            if(num==3)
            {
                printf("thread number:%d quit\n",num);
                int *p=NULL;
                *p=100;
            }
        }
    }
    int main()
    {
        pthread_t tid[5];
        int i = 0;
        for (i = 0; i < 5; i++)
        {
            pthread_create(tid + i, NULL, thread_run,(void*)&i);
            sleep(1);
        }
        while (1)
        {
            printf("我是主线程,我的thread ID是:%lu\n", pthread_self());
            printf("##############begin#################\n");
            int i = 0;
            for (i = 0; i < 5; i++)
            {
                printf("我创建的线程[%d]是:%lu\n", i, tid[i]);
            }
            printf("##############end###################\n");
            sleep(1);
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    结果展示:
    在这里插入图片描述

    二、线程等待

    一般而言,线程也是需要被等待的,如果不等待,可能会导致类似于”僵尸进程”的问题!

    函数名称pthread_join
    函数功能等待线程结束
    头文件#include
    函数原型int pthread_join(pthread_t thread, void **retval);
    参数thread:线程ID
    retval:它指向一个指针,用来存储被等待线程的返回值,
    意思就是,这是一个输出型参数,用来获取新线程退出时候,函数的返回值
    返回值成功返回0
    失败返回错误码
    // 线程等待
    #define NUM 1
    void *thread_run(void *args)
    {
        int num=*(int*)args;
        while (1)
        {
            printf("我是新线程[%d],我创建的线程的ID是:%lu\n", num, pthread_self());
            sleep(10);
            break;
        }
        return (void*)111;
    }
    int main()
    {
        pthread_t tid[NUM];
        int i = 0;
        for (i = 0; i < NUM; i++)
        {
            pthread_create(tid + i, NULL, thread_run,(void*)&i);
            sleep(1);
        }
        //指针变量,本身就可以充当某种容器保存数据
        void* status=NULL;
        pthread_join(tid[0],&status);
        printf("ret=%d\n",(int)status);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    结果展示:
    在这里插入图片描述
    我们的确实拿到线程退出的返回值。这里的返回值是int,也可以是其他类型的变量,也可以是对象的地址(不能是临时的)。

    三、线程终止

    线程终止的方案

    • 函数中return(1、main函数中return的时候代表主线程或者进程退出  2、其他线程函数return代表当前线程退出)
    • 新线程通过pthread_ exit终止自己( 而exit是终止进程,不要在其他线程中调用,如果你就想终止一个线程的话! ! )
    • 调用pthread_cancel取消一个线程
    函数名称pthread_exit
    函数功能结束调用线程
    头文件#include
    函数原型void pthread_exit(void *retval);
    参数指向退出信息的指针
    返回值
    // 线程终止
    #define NUM 1
    void *thread_run(void *args)
    {
        int num=*(int*)args;
        while (1)
        {
            printf("我是新线程[%d],我创建的线程的ID是:%lu\n", num, pthread_self());
            sleep(2);
            break;
        }
        pthread_exit((void*)123);
    }
    int main()
    {
        pthread_t tid[NUM];
        int i = 0;
        for (i = 0; i < NUM; i++)
        {
            pthread_create(tid + i, NULL, thread_run,(void*)&i);
            sleep(1);
        }
        //指针变量,本身就可以充当某种容器保存数据
        void* status=NULL;
        // 也要循环式等待线程
        for(i=0;i<NUM;i++)
        {
            pthread_join(tid[0],&status);
        }
        // 这里是int,也可以是其他类型的变量或者对象的地址(不能是临时的)
        printf("ret=%d\n",(int)status);
        sleep(3);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    结果展示:
    我们刚开始主线程+新创建的两个线程同时运行,新线程提前终止,主线程后终止退出,我们能看到明显的现象。
    在这里插入图片描述

    我们也是可以直接调用exit函数

    // 也可以直接调用exit函数
    exit(123);
    //pthread_exit((void*)123);
    
    • 1
    • 2
    • 3

    在这里插入图片描述

    函数名称pthread_cancel
    函数功能取消一个线程
    头文件#include
    函数原型int pthread_cancel(pthread_t thread);
    参数thread:要取消的线程ID
    返回值成功返回0
    失败返回错误码
    // pthread_cancel取消线程
    #define NUM 1
    void *thread_run(void *args)
    {
        int num=*(int*)args;
        while (1)
        {
            printf("我是新线程[%d],我创建的线程的ID是:%lu\n", num, pthread_self());
            sleep(2);
        }
    }
    int main()
    {
        pthread_t tid[NUM];
        int i = 0;
        for (i = 0; i < NUM; i++)
        {
            pthread_create(tid + i, NULL, thread_run,(void*)&i);
            sleep(1);
        }
        printf("wait sub thread...\n");
        sleep(5);
    
        printf("cancel sub thread..\n");
        pthread_cancel(tid[0]);
    
        void* status=NULL;
        for(i=0;i<NUM;i++)
        {
            pthread_join(tid[0],&status);
        }
        printf("ret=%d\n",(int)status);
        sleep(3);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    结果展示:
    在这里插入图片描述
    其中的退出结果ret=-1代表的是什么呢?
    在这里插入图片描述
    如果线程被其他线程调用pthread_cancel取消掉,value_ptr所指向的单元里面存放的常数是PTHREAD_CANCELED就是-1

    在新线程中取消主线程(实际中不推荐这样写)

    // 新线程取消主线程线程
    #define NUM 1
    pthread_t g_id;// 主线程ID定为全局的
    void *thread_run(void *args)
    {
        int num=*(int*)args;
        while (1)
        {
            printf("我是新线程[%d],我创建的线程的ID是:%lu\n", num, pthread_self());
            sleep(2);
            pthread_cancel(g_id);// 在新线程中取消主线程
        }
    }
    int main()
    {
        g_id=pthread_self();// 拿到主线程的线程ID
        pthread_t tid[NUM];
        int i = 0;
        for (i = 0; i < NUM; i++)
        {
            pthread_create(tid + i, NULL, thread_run,(void*)&i);
            sleep(1);
        }
        printf("wait sub thread...\n");
        sleep(50);// 50秒内主线程创建新线程,并且不退出
    
        printf("cancel sub thread..\n");
        pthread_cancel(tid[0]);
    
        void* status=NULL;
        for(i=0;i<NUM;i++)
        {
            pthread_join(tid[0],&status);
        }
        printf("ret=%d\n",(int)status);
        sleep(3);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    结果展示:
    确实主线程被取消了,但是进程却没有退出,会出现类似于“僵尸进程”的现象
    在这里插入图片描述

    四、线程分离

    上述列举的是几种等待的情况,但是我们不想等待呢?

    • 在任何一个时间点上,线程是可结合的(joinable),或者是分离的(detached)。一个可结合的线程能够被其他线程回收其资源和杀死;在被其他线程回收资源之前,它的存储器资源是不释放的。相反,一个分离的线程是不能被其他线程回收或杀死的,它的存储器资源在它终止时由系统自动释放。
    • 线程的分离状态决定一个线程以什么样的方式来终止自己。在默认情况下线程是非分离状态的,在这种情况下,原有的线程等待创建的线程结束。只有当pthread_ join() 函数返回时,创建的线程才算终止,才能释放已占用的系统资源。而分离线程如果没有被其他的线程所等待,当该线程运行结束,线程就终止了,同时也会立刻释放系统资源。线程分离使用pthread_ detach()实现的。
      (可以类比之前信号中所学的signal(SIGCHLD,SIG_IGN))。
    函数名称pthread_detach
    函数功能使线程处于分离状态
    头文件#include
    函数原型int pthread_detach(pthread_t threadID)
    参数threadID:要分离的线程
    返回值0:成功
    错误号:失败
    // 线程分离
    #define NUM 1
    void *thread_run(void *args)
    {
        pthread_detach(pthread_self());
        int num=*(int*)args;
        while (1)
        {
            printf("我是新线程[%d],我创建的线程的ID是:%lu\n", num, pthread_self());
            sleep(2);
            break;
        }
        return (void*)(111);
    }
    int main()
    {
        pthread_t tid[NUM];
        int i = 0;
        for (i = 0; i < NUM; i++)
        {
            pthread_create(tid + i, NULL, thread_run,(void*)&i);
            sleep(1);
        }
        printf("wait sub thread...\n");
        sleep(1);
        printf("cancel sub thread..\n");
    
        void* status=NULL;
        int ret=0;
        for(i=0;i<NUM;i++)
        {
            ret=pthread_join(tid[0],&status);
        }
        printf("ret:%d status:%d\n",ret,(int)status);
        sleep(3);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    结果展示:
    拿到了ret和status
    在这里插入图片描述

    五、线程ID以及进程地址空间布局

    pthread_ create函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和前面说的线程ID不是一回事。
    前面讲的线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程。
    pthread_ create函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID, 属于NPTL(NATIVE POSIX Thread Library)线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的。
    线程库NPTL提供了pthread_ self函数,可以获得线程自身的ID。

    void* thread_run(void*args)
    {
         while(1)
        {
            printf("%s id:0x%x\n",(const char*)args,pthread_self());
            sleep(2);
        }
    }
    int main()
    {
        pthread_t tid;
        pthread_create(&tid,NULL,thread_run,(void*)"new thread");
        while(1)
        {
            printf("main thread id:0x%x\n",pthread_self());
            sleep(1);
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    在这里插入图片描述
    我们查看到的线程id是pthread库的线程id,不是Linux内核中的LWP,pthread库的线程id是一个内存地址!(虚拟地址)
    在这里插入图片描述
    在这里插入图片描述
    每个线程都要又运行时的临时数据,每个线程都要又自己的私有栈结构!
    所以使用了pthread的数据结构:pthread_t,这个结构用来标识线程的ID。

    我们如何快速的找到每个特定的线程呢?
    我们只需要拿到每个pthread_t id(虚拟地址),那么我们就能在快速的找到这个线程的所有的信息。

    用户级线程和内核级线程1:1对应
    在这里插入图片描述

  • 相关阅读:
    二、【react-redux】react-redux优化
    windows11系统没有系统散热方式的解决办法
    ProxyChains图文教程
    以detectron2了解maskrcnn实现源码(0)--开篇
    虚拟机Ubuntu20.04 网络连接器图标开机不显示怎么办
    简单开发调用百度翻译api(java)
    ubuntu 上vscode使用cmake编译运行c++程序
    java毕业生设计新城街道社区的健康档案管理平台计算机源码+系统+mysql+调试部署+lw
    【前端面试问题总结】2022.9.18
    关于跨域问题
  • 原文地址:https://blog.csdn.net/weixin_57675461/article/details/124991467