1. Linux线程控制

1. ps -aL:查看Linux操作系统的所有线程
在这里插入图片描述
lwp:light weight process:轻量级线程,所以进程调度时操作系统看lwp和pid就可以分清进程和线程,如果切换task_struct时,task_struct里的pid和上次被调度的进程的pid相同,但是lwp变了就是线程调度(轻量级进程调度),如果task_struct里的lwp和上次被调度的进程的pid不同就是进程调度

进程调度的时间片是均分给线程的,异常之后任意一个线程崩溃,进程就崩溃了,所以健壮性低

在这里插入图片描述

pthread_create是创建线程的接口,传入rout函数名,新创建的线程就会执行那个函数方法,此时线程就可以在同一个虚拟地址空间访问不同函数了,函数内部都是虚拟地址,都是资源,所以代码区不需要我们划分,只要有函数入口地址,线程天然就可以访问同一个虚拟地址空间代码区的不同资源了

7.1 pthread库

在这里插入图片描述
Linux操作系统只有轻量级进程的概念,没有线程的概念,所以pthread库是封装给用户用的,右面是创建轻量级进程的系统调用,所以在要-l指明库,因为这个库不是c语言标准库,否则链接报错
在这里插入图片描述
当我们加上-l 链接成功后,运行该可执行程序发现消息混乱,因为多线程都是向显示器文件打印,此时显示器文件就是共享资源,会有信息安全的问题,就像命名管道一样
在这里插入图片描述

7.2 C++线程库

在这里插入图片描述
所以C++也要-l指明pthread库

7.3 Linux线程控制接口

1. 创建线程

在这里插入图片描述

成功0被返回,失败错误码被设置
1. 第1个参数:输出型参数,得到线程id
2. 第2个参数:线程属性,一般设置为nullptr,默认
3. 新线程要执行的函数入口,一个函数指针
4. 传递给函数指针的参数

2. 等待线程

在这里插入图片描述

void fun()
{
    printf("666\n");
}
void *rout(void *arg)
{
    int n=1;
    while (n--)
    {
       // fun();
        printf("I am thread 1\n");
        sleep(1);
    }
    return nullptr;
}
int main(void)
{
    pthread_t tid;
    int ret=1;
    pthread_create(&tid, NULL, rout, NULL);
    while (ret--)
    {
         //fun();
        printf("I'am main thread2\n");
        sleep(1);
    }
    pid_t n=pthread_join(tid,nullptr);
    printf("tid:%ld\n",tid);
}

新线程创建好,要被主线程等待(main函数里的线程),否则类似僵尸进程问题,内存泄漏,主进程join就可以解决内存泄漏问题

这个fun函数可以被主线程和新线程并行访问,此时称这个函数被重入了,但是没有出现问题,所以这个函数是可重入函数

pid_t n=pthread_join(tid,nullptr);
主线程可以拿到新线程的tid,但这个tid是一个无符号长整数,并不是lwp,它也不该是lwp,因为轻量级进程是Linux操作系统的概念,但用户只认线程,所以不能一样,lwp是用来区分调度的
在这里插入图片描述

3.线程终止

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

1. 线程不能用exit(int)终止,因为这是终止进程的方法,一个线程调用所以线程就都没了
2. 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit
3. 线程可以调用pthread_exit终止自己
4. ⼀个线程可以调用pthread_cancel终止同一进程中的另⼀个线程,一般是主线程终止新建线程,但是一定要保证新线程已经启动,要不你取消啥呢

4. 分离线程

在这里插入图片描述

默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则
无法释放资源,从而造成系统泄漏,但是进程那里可以使用SIGCHLD,SIG_IGN,可以让父进程不用等待子进程,OS自动释放子进程的资源,线程也可以,可以主线程分离新线程:pthread_detach(tid);
也可以新线程分离自己:pthread_detach(pthread_self());
此时主线程无需等待新线程,新线程的资源被OS自动回收

joinable和分离是冲突的,⼀个线程不能既是joinable又是分离的,所以分离后join一定会失败,
判断join的返回值!=0证明等待失败分离成功

5. pthread_self

在这里插入图片描述
谁调用这个函数就获取谁的tid

void *rout(void *arg)
{
    int n = 1;
    while (n--)
    {
        // fun();
        printf("I am thread 1\n");
        sleep(1);
    }
    pid_t s = pthread_self();
    printf("tid1:0x%x\n", s); // 新线程

    return nullptr;
}
int main(void)
{
    pthread_t tid;
    int ret = 1;
    pthread_create(&tid, NULL, rout, NULL);
    while (ret--)
    {
        // fun();
        printf("I'am main thread2\n");
        sleep(1);
    }
    pid_t n = pthread_join(tid, nullptr);
    pid_t s = pthread_self();
    printf("tid1:0x%x\n", tid); // 新线程
    printf("tid:0x%x\n", s);    // 主线程
}

在这里插入图片描述

6. 主线程可以拿到新线程的退出码

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <iostream>
using namespace std;
void fun()
{
    printf("666\n");
}
void *rout(void *arg)
{
    int n = 1;
    while (n--)
    {
        // fun();
        printf("I am thread\n");
        sleep(1);
    }
    pid_t s = pthread_self();
    printf("tid1:0x%x\n", s); // 新线程
    return (void *)100;
}
int main(void)
{
    pthread_t tid;
    //int ret = 1;
    int a=1;
    pthread_create(&tid, NULL, rout, NULL);
    while (a--)
    {
        // fun();
        printf("I'am main thread2\n");
        sleep(1);
    }
    void *ret = nullptr;
    pid_t n = pthread_join(tid, &ret);
    cout << (long long int)ret << endl;
    return 0;
}

在这里插入图片描述
新线程返回的是新线程的退出码,主线程可以拿到,第二个参数是输出型参数返回的是void的地址,用void接收,相当于你要传&ret,内部才能void对你的ret进行修改,否则就是值拷贝,修改不了ret,所以必须传&ret,然后才能修改,但是void类型不能解引用,所以必须强转成longlong类型,ret就是100强转后的地址,在强转回来就可以拿到退出码

为什么线程没有退出信号或core dump呢?
因为线程一旦异常进程就会挂,所有线程都挂了,拿到也没意义,也拿不到,因为你不知道哪个线程出的错,只能知道进程出错了,所以信号是进程要处理的话题,join都是基于线程健康跑完的情况,不需要处理异常信号

主线程和新线程谁先运行不确定,跟进程一样

2. pthread库的深刻理解

在这里插入图片描述
库也是ELF文件要加载到内存,映射到虚拟地址空间,然后我们写的代码就可以访问库的任意方法了,线程的概念是库维护的,在库内部就一定会存在多个被创建好的线程,库要先描述再组织的管理线程,所以当我们调用pthread_create时,库就会在内部把这些东西创建好,并维护类似于这样的数据结构
所以Linux所有线程都在库中
优先级,时间片,上下文是进程的概念,进程内部lwp会保存,所以线程一部分在库里描述,一部分在进程内部描述
在这里插入图片描述
在这里插入图片描述
这就是pthread库在虚拟地址空间创建的数据块,内部主要包含这些,struct_pthread内部包含tid,但其实tid就是线程在库中对应数据块的起始虚拟地址,就是struct pthread的地址,每个线程都要有自己独立的栈空间,这三个部分构成TCB,所以线程需要join,如果不join,TCB无法释放内存泄漏,struct_pthread内部有voidret,当一个新线程运行完返回return (void)100,其实是返回给内部的ret,然后在传给我们join的&s的,因为join可以拿到这个数据块的其实虚拟地址,所以可以访问到ret

在这里插入图片描述
所以当我们调用pthread_create时,库会在库内部创建线程控制的管理块,也要在内核中,调用创建轻量级进程的系统调用,执行什么方法用户自己传,栈是库内部自己创建的,再把栈传过去,所以进程在调度时产生的临时数据就可以放到栈里了,轻量级进程调用routine时会形成栈帧,就是形成在自己的栈结构里,就是库传入的stack地址
在这里插入图片描述
其中n就是临时数据,但其实主线程是可以访问新线程的栈的,一个线程的所有资源都可以被另一个线程看到,因为线程共享虚拟地址空间,每个线程都有自己独立的栈,因为我们一般不会让一个线程去访问另一个线程的栈

在这里插入图片描述
setname:设置名字,它是线程局部存储,其实就是把字符串写到线程局部存储里
getname:得到名字,直接从刚才写入的线性局部存储里拿字符串,只有你一个人访问,不存在并发问题,因为他要tid

线程共享资源,所以是同一个全局变量做修改,但是如果不想让多个线程对同一个全局变量修改,就可以在全局变量前面加上__thread int count,此时相当于在多个线程的线程局部存储里创建该变量,就是每个线程都有自己的count,此时虚拟地址不同,访问时就各自从线程局部存储访问自己的count变量,不会对同一个变量访问了

线程局部存储只能存储内置类型和部分指针

3. 封装线程

main.cc

#include "thread.hpp"
using namespace ThreadModlue;
#include <unistd.h>
#include<vector>
int main()
{
    Thread t([]()
             {
                 while (true)
                     {
                         char name[128];
                   
                        std::cout << "我是一个新线程: " << name << std::endl; // 我的线程的名字是什么呀?debug
                         sleep(1);
                    } });
    t.start();
    t.Detach();
    sleep(5);

    t.stop();

    sleep(5);

    t.join();
    // std::vector<Thread> threads;
    // for (int i = 0; i < 10; i++)
    // {
    //     threads.emplace_back([]()
    //                          {
    //     while(true)
    //     {
    //         char name[128];
    //         std::cout << "我是一个新线程: " << name << std::endl; // 我的线程的名字是什么呀?debug
    //         sleep(1);
    //     } });
    // }
    // for (auto &thread : threads)
    // {
    //     thread.start();
    // }

    // for (auto &thread : threads)
    // {
    //     thread.join();
    // }
    return 0;
}

Makefile

thread:main.cc
	g++ -o $@ $^ -l pthread -std=c++11
.PHONY:clean
clean:
	rm -f thread

Thread.hpp

#pragma once
#include <iostream>
using namespace std;
#include <string>
#include <pthread.h>
#include <cstdio>
#include <cstring>
#include <functional>
namespace ThreadModlue
{

    static uint32_t number = 1;
    class Thread
    {
    private:
        using func_t = function<void()>;
        void EnableDetach()
        {
            cout << "线程被分离了" << endl;
            _isdetach = true;
        }
        void EnableRunning()
        {
            _isrunning = true;
        }
        static void *routine(void *args) // 必须静态否则多一个参数
        {
            Thread *self = static_cast<Thread *>(args);
            self->EnableRunning();
            if (self->_isdetach)
                self->Detach();
            self->_func(); // 回调
            return nullptr;
        }

    public:
        Thread(func_t func)
            : _tid(0), _isdetach(false), _isrunning(false), res(nullptr), _func(func)
        {
            _name = "thread-" + to_string(number++);
        }
        void Detach()
        {
            if (_isdetach)
            {
                return;
            }
            if (_isrunning)
            {
                pthread_detach(_tid);
            }
            EnableDetach();
        }
        bool start()
        {
            if (_isrunning)
            {
                return false;
            }
            int n = pthread_create(&_tid, nullptr, routine, this); // 需要传this来回调
            if (n != 0)
            {
                std::cerr << "create thread error: " << strerror(n) << std::endl;
                return false;
            }
            else
            {
                std::cout << _name << " create success" << std::endl;
                return true;
            }
        }
        bool stop()
        {
            if (_isrunning)
            {
                int n = pthread_cancel(_tid);
                if (n != 0)
                {
                    std::cerr << "stop thread error: " << strerror(n) << std::endl;
                    return false;
                }
                else
                {
                    _isrunning = false;
                    std::cout << _name << " stop" << std::endl;
                    return true;
                }
            }
            return false;
        }
        void join()
        {
            if (_isdetach)
            {
                std::cout << "你的线程已经是分离的了,不能进行join" << std::endl;
                return;
            }
            else
            {
                int n = pthread_join(_tid, &res);
                if (n != 0)
                {
                    std::cerr << "create thread error: " << strerror(n) << std::endl;
                }
                else
                {
                    std::cout << "join success" << std::endl;
                }
            }
        }
        ~Thread()
        {
        }

    private:
        string _name;
        pthread_t _tid;
        bool _isdetach;
        bool _isrunning;
        void *res;
        func_t _func;
    };
}
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐