【Linux】第四十一站:线程控制

一、Linux线程VS进程

1.进程和线程

  • 进程是资源分配的基本单位
  • 线程是调度的基本单位
  • 线程共享进程数据,但也拥有自己的一部分数据:
  • 线程ID
  • 一组寄存器(上下文)
  • errno
  • 信号屏蔽字
  • 调度优先级

2.进程的多个线程共享

同一地址空间,因此Text Segment、Data Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:

  • 文件描述符表
  • 每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)
  • 当前工作目录
  • 用户id和组id

进程和线程的关系如下图:

image-20240228182731477

3.关于进程线程的问题

如何看待之前学习的单进程?具有一个线程执行流的进程

二、线程控制

1.POSIX线程库

内核中有没有很明确的线程的概念呢?没有的。它只有轻量级进程的概念。

所以它就一定无法给我们提供线程的系统调用,只会给我们提供轻量级进程系统调用!

可是我们用户,需要的是线程的接口!**所以我们的应用层就有了一个pthread线程库。它是将轻量级进程接口进行了封装。为用户提供直接线程的接口。**对于这个pthread线程库,几乎所有的linux平台,都是默认自带这个库的!在Linux中编写多线程代码,需要使用第三方pthread库!

  • 与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”打头的
  • 要使用这些函数库,要通过引入头文<pthread.h>
  • 链接这些线程函数库时要使用编译器命令的“-lpthread”选项

2.快速使用一些常见的接口

2.1 创建线程

#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
  • thread:返回线程ID,是一个输出型参数
  • attr:设置线程的属性,大部分情况下,attr为NULL表示使用默认属性
  • start_routine:是个函数地址,线程启动后要执行的函数,返回值为void*,参数也是void*,他是新线程所执行的入口函数。void*可以接收或返回任意指针类型,因为C语言没有模板,但是想用泛型。注意在linux平台下,指针是8字节,因为是64位机器,并且void的大小是1,且不可以形如void x这样定义变量
  • arg:传给线程启动函数的参数。创建线程成功,新线程回调线程函数的时候,需要参数,这个参数就是给线程函数传递的。没有就设置位nullptr
  • 返回值:成功返回0;失败返回错误码。

image-20240228204445742

然后我们用如下代码来进行验证

#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;

void* threadRountine(void* args)
{
    while(true)
    {
        cout << "new thread, pid" << getpid() << endl;
        sleep(2);
    }
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRountine, nullptr);
    while(true)
    {
        cout << "main thread, pid" << getpid() << endl;
        sleep(1);
    }

    return 0;
}

运行结果为

image-20240228211122837

注意我们可以发现,它只有一个pid,说明它确实是一个进程,只不过一个进程里有两个执行流

可是如果我们就想查到两个该怎么办呢?我们可以用如下命令

ps -aL   #这里的L我们可以理解为light轻的意思,也就是轻量级进程

image-20240228211321454

其中第二个LWP(light weight process)其实就是轻量级进程的意思。因为轻量级进程也需要有一个编号

其中有一个PID等于LWP,这里说明了这个线程是主线程,剩下的是被创建出来的线程

像我们以前写的单线程代码,其实就是PID永远等于LWP的。

除此之外,一个进程中的任何一个线程被干掉了,那么整个进程都会被干掉

image-20240228212113778

那么这个信号是发给进程还是线程的呢?,其实是发给进程的。因为线程只是进程的一个执行分支。这也就是为什么线程的健壮性很差,因为一个线程被干掉了,其他线程也会被干掉。


我们在看一下下面的代码:

#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>
using namespace std;

void show(const string& name)
{
    cout << name << "say# " << "hello thread" << endl;
}

void* threadRountine(void* args)
{
    while(true)
    {
        cout << "new thread, pid" << getpid() << endl;
        show("[new thread]");
        sleep(2);
    }
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRountine, nullptr);
    while(true)
    {
        cout << "main thread, pid" << getpid() << endl;
        show("[main thread]");
        sleep(1);
    }

    return 0;
}

运行结果为

image-20240228212519920

我们可以看到主线程和新线程都调用了这个方法,说明这个函数可以被多个执行流同时执行。即show函数被重入!


我们还可以看下面的代码

#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>
using namespace std;
int g_val = 100;

void show(const string& name)
{
    cout << name << "say# " << "hello thread" << endl;
}

void* threadRountine(void* args)
{
    while(true)
    {
        printf("new thread, pid:%d, g_val:%d, &g_val:0x%p\n", getpid(), g_val, &g_val);
        // cout << "new thread, pid" << getpid() << endl;
        //show("[new thread]");
        sleep(2);
    }
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRountine, nullptr);
    while(true)
    {
        printf("main thread, pid:%d, g_val:%d, &g_val:0x%p\n", getpid(), g_val, &g_val);
        // cout << "main thread, pid" << getpid() << ", g_val" << g_val << "&g_val" << &g_val << endl;
        //show("[main thread]");
        g_val++;
        sleep(1);
    }

    return 0;
}

运行结果为

image-20240228214233804

我们可以看到,主线程和新线程都可以看到这个变量被修改了。说明两个线程共享这个变量。

所以两个线程想要进行通信实在是太容易了


我们再用下面的代码进行测试

#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>
using namespace std;
int g_val = 100;

void show(const string& name)
{
    cout << name << "say# " << "hello thread" << endl;
}

void* threadRountine(void* args)
{
    while(true)
    {
        printf("new thread, pid:%d, g_val:%d, &g_val:0x%p\n", getpid(), g_val, &g_val);
        // cout << "new thread, pid" << getpid() << endl;
        //show("[new thread]");
        sleep(5);
        int a = 10;
        a = a / 0;
    }
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRountine, nullptr);
    while(true)
    {
        printf("main thread, pid:%d, g_val:%d, &g_val:0x%p\n", getpid(), g_val, &g_val);
        // cout << "main thread, pid" << getpid() << ", g_val" << g_val << "&g_val" << &g_val << endl;
        //show("[main thread]");
        g_val++;
        sleep(1);
    }

    return 0;
}

运行结果为:

image-20240228214854610

这就是因为一个线程出现异常了,所以导致整个进程挂掉了


我们接下来在看一下这个tid是什么

#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>
using namespace std;
int g_val = 100;

void show(const string& name)
{
    cout << name << "say# " << "hello thread" << endl;
}

void* threadRountine(void* args)
{
    while(true)
    {
        printf("new thread, pid:%d, g_val:%d, &g_val:0x%p\n", getpid(), g_val, &g_val);
        // cout << "new thread, pid" << getpid() << endl;
        //show("[new thread]");
        sleep(1);
        // int a = 10;
        // a = a / 0;
    }
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRountine, nullptr);
    while(true)
    {
        printf("main thread, pid:%d, g_val:%d, &g_val:0x%p, creat new thread tid:%p\n", getpid(), g_val, &g_val, tid);
        // cout << "main thread, pid" << getpid() << ", g_val" << g_val << "&g_val" << &g_val << endl;
        //show("[main thread]");
        g_val++;
        sleep(1);
    }

    return 0;
}

运行结果为

image-20240228215738505

其实这个tid显然不是这个LWP,因为LWP是操作系统认识的就可以了,这tid是我们用户所使用的。至于它的具体使用,我们稍后再谈。


我们再来看看第四个参数,这个第四个参数是给第三个函数进行传参的。

#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>
using namespace std;
int g_val = 100;

void show(const string& name)
{
    cout << name << "say# " << "hello thread" << endl;
}

void* threadRountine(void* args)
{
    const char* name = (const char*)args;
    while(true)
    {
        printf("%s, pid:%d, g_val:%d, &g_val:0x%p\n", name, getpid(), g_val, &g_val);
        // cout << "new thread, pid" << getpid() << endl;
        //show("[new thread]");
        sleep(1);
        // int a = 10;
        // a = a / 0;
    }
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRountine, (void*)"thread 1");
    while(true)
    {
        printf("main thread, pid:%d, g_val:%d, &g_val:0x%p, creat new thread tid:%p\n", getpid(), g_val, &g_val, tid);
        // cout << "main thread, pid" << getpid() << ", g_val" << g_val << "&g_val" << &g_val << endl;
        //show("[main thread]");
        g_val++;
        sleep(1);
    }

    return 0;
}

运行结果为

image-20240228220924459

我们果然看到这个第四个参数被传入了进去


2.2 线程等待

那么这两个线程谁先进行退出呢?一般来说是新线程先退出的,然后主线程才能退出的,因为是主线程创建的它,它要对这个新线程进行管理。

如果我们主线程是一个死循环,而新线程一直不退出,那么也会造成类似于进程中的僵尸进程的问题(当然线程里没有这个说法)。所以新线程被创建出来以后,一般也要被等待,如果不等待,可能会造成类似于僵尸进程的问题。当然这个问题我们是无法验证出来的,因为新线程一退,我们查也就查不到了。但是确确实实会存在这个问题。

更重要的是,我们将新线程创建出来,就是让他就办事的,我们得知道它办的怎么样,结果数据是什么?

所以我们线程等待的两个目的:

  1. 防止内存泄漏
  2. 如果需要,我们也可以获取一下子进程的退出结果

下面是线程等待的函数

#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
//Compile and link with -pthread.

如果成功返回0,失败返回错误码。注意:线程里面所有的函数都不用errno错误码,而是直接返回一个错误码。这就保证了所有的线程都可以有一个返回的错误码,不需要去抢占全局的那个变量

关于参数:

  1. 第一个参数是线程的tid

  2. 第二个参数是该线程结束时的返回值。注意*retval才是void*类型,也就是*retval才是函数的返回值

如下图所示,当void*通过pthread_join的方式传递的时候,会产生一个临时变量。比如说,我们调用函数的时候传递&x,那么&x其实会被拷贝一份,我们这里暂且记作retavl。然后在pthread_join内部执行,*retval = z这一步。最终就成功的为x赋值了。即x就相当于一个输入型参数。

image-20240229134004594

我们可以用如下代码来进行操作一下

#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>
using namespace std;
int g_val = 100;

void show(const string& name)
{
    cout << name << "say# " << "hello thread" << endl;
}

void* threadRountine(void* args)
{
    const char* name = (const char*)args;
    int cnt = 5;
    while(true)
    {
        printf("%s, pid:%d, g_val:%d, &g_val:0x%p\n", name, getpid(), g_val, &g_val);
        // cout << "new thread, pid" << getpid() << endl;
        //show("[new thread]");
        sleep(1);
        // int a = 10;
        // a = a / 0;
        cnt--;
        if(cnt == 0)
        {
            break;
        }
   }
   return nullptr; //走到这里就默认线程退出了。
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRountine, (void*)"thread 1");
    // while(true)
    // {
    //     printf("main thread, pid:%d, g_val:%d, &g_val:0x%p, creat new thread tid:%p\n", getpid(), g_val, &g_val, tid);
    //     // cout << "main thread, pid" << getpid() << ", g_val" << g_val << "&g_val" << &g_val << endl;
    //     //show("[main thread]");
    //     g_val++;
    //     sleep(1);
    // }
    sleep(7);
    pthread_join(tid, nullptr);
    cout << "main thread quit..." << endl; 
    return 0;
}

运行结果为:

image-20240229125333657

我们可以很明显的看到,新线程先退出了,主线程等两秒之后也就退出了。这里我们观察不到新线程有类似于僵尸的状态,但是确确实实是存在的这个状态


我们现在来使用一下第二个参数retval

#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>
using namespace std;
int g_val = 100;

void show(const string& name)
{
    cout << name << "say# " << "hello thread" << endl;
}

void* threadRountine(void* args)
{
    const char* name = (const char*)args;
    int cnt = 5;
    while(true)
    {
        printf("%s, pid:%d, g_val:%d, &g_val:0x%p\n", name, getpid(), g_val, &g_val);
        // cout << "new thread, pid" << getpid() << endl;
        //show("[new thread]");
        sleep(1);
        // int a = 10;
        // a = a / 0;
        cnt--;
        if(cnt == 0)
        {
            break;
        }
   }
   //return nullptr; //走到这里就默认线程退出了。
   return (void*)1; //走到这里就默认线程退出了。
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRountine, (void*)"thread 1");
    // while(true)
    // {
    //     printf("main thread, pid:%d, g_val:%d, &g_val:0x%p, creat new thread tid:%p\n", getpid(), g_val, &g_val, tid);
    //     // cout << "main thread, pid" << getpid() << ", g_val" << g_val << "&g_val" << &g_val << endl;
    //     //show("[main thread]");
    //     g_val++;
    //     sleep(1);
    // }
    //sleep(7);
    void* retval;
    pthread_join(tid, &retval); //main thread等待的时候,默认是阻塞等待的
    cout << "main thread quit..., ret: " << (long long)retval << endl; 


    return 0;
}

运行结果为,可以看到我们确实已经拿到了1

image-20240229140543870

不过这里我们会感觉到哪里不对劲,为什么我们在这里join的时候不考虑异常呢??

其实是因为做不到,因为线程一旦出异常了,主线程也就挂了。所以线程这里不用考虑异常,异常这里是进程考虑的。

2.3 线程终止

如果我们想要终止线程,能否像进程终止一样使用exit函数呢?

我们可以用下面代码来验证一下

#include <iostream>
#include <pthread.h>
#include <string>
#include <cstdlib>
#include <unistd.h>
using namespace std;
int g_val = 100;

void show(const string& name)
{
    cout << name << "say# " << "hello thread" << endl;
}

void* threadRountine(void* args)
{
    const char* name = (const char*)args;
    int cnt = 5;
    while(true)
    {
        printf("%s, pid:%d, g_val:%d, &g_val:0x%p\n", name, getpid(), g_val, &g_val);
        // cout << "new thread, pid" << getpid() << endl;
        //show("[new thread]");
        sleep(1);
        // int a = 10;
        // a = a / 0;
        cnt--;
        if(cnt == 0)
        {
            break;
        }
   }
   exit(11);
   //return nullptr; //走到这里就默认线程退出了。
   //return (void*)1; //走到这里就默认线程退出了。
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRountine, (void*)"thread 1");
    // while(true)
    // {
    //     printf("main thread, pid:%d, g_val:%d, &g_val:0x%p, creat new thread tid:%p\n", getpid(), g_val, &g_val, tid);
    //     // cout << "main thread, pid" << getpid() << ", g_val" << g_val << "&g_val" << &g_val << endl;
    //     //show("[main thread]");
    //     g_val++;
    //     sleep(1);
    // }
    //sleep(7);
    void* retval;
    pthread_join(tid, &retval); //main thread等待的时候,默认是阻塞等待的
    cout << "main thread quit..., ret: " << (long long)retval << endl; 


    return 0;
}

运行结果为

image-20240229141548706

我们可以注意到,主线程并没有打印出对应的main thread quit…。所以说明exit直接将主线程也终止了。

即exit是用来终止进程的!,不能用来直接终止线程

线程终止的接口如下所示:

#include <pthread.h>
void pthread_exit(void *retval);
//Compile and link with -pthread.

它的作用是终止调用这个函数的线程,谁调用它就终止谁。参数是void*,和这个函数的返回值的含义是一样的。

我们用如下代码来进行测试

#include <iostream>
#include <pthread.h>
#include <string>
#include <cstdlib>
#include <unistd.h>
using namespace std;
int g_val = 100;

void show(const string& name)
{
    cout << name << "say# " << "hello thread" << endl;
}

void* threadRountine(void* args)
{
    const char* name = (const char*)args;
    int cnt = 5;
    while(true)
    {
        printf("%s, pid:%d, g_val:%d, &g_val:0x%p\n", name, getpid(), g_val, &g_val);
        // cout << "new thread, pid" << getpid() << endl;
        //show("[new thread]");
        sleep(1);
        // int a = 10;
        // a = a / 0;
        cnt--;
        if(cnt == 0)
        {
            break;
        }
    }
    pthread_exit((void*)100);

   //exit(11);
   //return nullptr; //走到这里就默认线程退出了。
   //return (void*)1; //走到这里就默认线程退出了。
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRountine, (void*)"thread 1");
    // while(true)
    // {
    //     printf("main thread, pid:%d, g_val:%d, &g_val:0x%p, creat new thread tid:%p\n", getpid(), g_val, &g_val, tid);
    //     // cout << "main thread, pid" << getpid() << ", g_val" << g_val << "&g_val" << &g_val << endl;
    //     //show("[main thread]");
    //     g_val++;
    //     sleep(1);
    // }
    //sleep(7);
    void* retval;
    pthread_join(tid, &retval); //main thread等待的时候,默认是阻塞等待的
    cout << "main thread quit..., ret: " << (long long)retval << endl; 
    return 0;
}

运行结果为

image-20240229142031499

上面是新线程去调用pthread_exit接口,那么只有这个线程会退出,如果主线程去调用这个接口退出的话,那么整个进程都会终止

2.4 线程取消

如下所示,是线程取消的接口。

#include <pthread.h>
int pthread_cancel(pthread_t thread);
//Compile and link with -pthread.

我们用如下代码来进行验证

#include <iostream>
#include <pthread.h>
#include <string>
#include <cstdlib>
#include <unistd.h>
using namespace std;
int g_val = 100;

void show(const string& name)
{
    cout << name << "say# " << "hello thread" << endl;
}

void* threadRountine(void* args)
{
    const char* name = (const char*)args;
    int cnt = 5;
    while(true)
    {
        printf("%s, pid:%d, g_val:%d, &g_val:0x%p\n", name, getpid(), g_val, &g_val);
        // cout << "new thread, pid" << getpid() << endl;
        //show("[new thread]");
        sleep(1);
        // int a = 10;
        // a = a / 0;
        cnt--;
        if(cnt == 0)
        {
            break;
        }
    }


    //pthread_exit((void*)100);

   //exit(11);
   //return nullptr; //走到这里就默认线程退出了。
   //return (void*)1; //走到这里就默认线程退出了。
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRountine, (void*)"thread 1");
    // while(true)
    // {
    //     printf("main thread, pid:%d, g_val:%d, &g_val:0x%p, creat new thread tid:%p\n", getpid(), g_val, &g_val, tid);
    //     // cout << "main thread, pid" << getpid() << ", g_val" << g_val << "&g_val" << &g_val << endl;
    //     //show("[main thread]");
    //     g_val++;
    //     sleep(1);
    // }
    //sleep(7);
    sleep(1); //保证新线程已经启动

    pthread_cancel(tid);

    void* retval;
    pthread_join(tid, &retval); //main thread等待的时候,默认是阻塞等待的
    cout << "main thread quit..., ret: " << (long long)retval << endl; 


    return 0;
}

运行结果为

image-20240229150734028

我们可以注意到,此时这个线程等待以后的返回值为-1

其实是因为一个线程如果被取消的话,会有这样一个宏

#define PTHREAD_CANCELED ((void *) -1)

换句话说,如果线程是被取消的,那么它退出时的返回码就是-1,即上面的宏

2.5 综合使用前面的四个接口

其实线程的参数和返回值,不仅仅可以用来传递一般参数,也可以传递对象

我们可以用下面的代码来看

#include <iostream>
#include <pthread.h>
#include <string>
#include <cstdlib>
#include <unistd.h>
using namespace std;

class Request
{
public:
    Request(int start, int end, const string& threadname)
        :_start(start)
        ,_end(end)
        ,_threadname(threadname)
    {}
public:
    int _start;
    int _end;
    string _threadname;
};
class Response
{
public:
    Response(int result, int exitcode)
        :_result(result)
        ,_exitcode(exitcode)
    {}
public:
    int _result; //计算结果
    int _exitcode; //计算结果是否可靠
};

void* sumCount(void* args) //线程的参数和返回值,不仅仅可以用来传递一般参数,也可以传递对象
{
    Request *rq = static_cast<Request*>(args);
    Response *rsp = new Response(0, 0);
    for(int i = rq->_start; i <= rq->_end; i++)
    {
        cout << rq->_threadname << " is runing, caling..., " << i << endl;
        rsp->_result += i;
        usleep(100000);
    }
    delete rq;
    return (void*)rsp;
}

int main()
{
    pthread_t tid;
    Request* rq = new Request(1, 100, "thread 1"); 
    pthread_create(&tid, nullptr, sumCount, rq);
    void* ret;
    pthread_join(tid, &ret);
    Response *rsp = static_cast<Response*>(ret);
    cout << "rsp->result: " << rsp->_result << ", exitcode: " << rsp->_exitcode << endl; 
    delete rsp;
    return 0;
}

运行结果为

image-20240229154954736

所以它就可以用来求出和。让每一个线程只执行其中的一部分计算,然后我们自己在将这些结果合并起来。

并且我们发现,我们的这些对象都是在堆区创建的。并且我们是交叉使用的,说明堆空间的也是被线程共享使用的

2.6 C++11中的线程

目前,我们使用的是原生线程库(pthread库)

其实C++11 语言本身也已经支持多线程了,它与我们的原生线程库有什么关系呢?

C++11的线程需要用下面的库

#include<thread>

我们看下面的代码

#include <iostream>
#include <pthread.h>
#include <string>
#include <cstdlib>
#include <unistd.h>
#include <thread>
using namespace std;
void threadrun()
{
    while(true)
    {
        cout << "I am a new thread for C++" << endl;
        sleep(1);
    }
}

int main()
{
    thread t1(threadrun);
    t1.join();
    return 0;
}

运行结果为

image-20240229155939100

我们需要注意的是,C++11中的线程库其实底层还是封装了linux提供的系统调用接口,所以我们编译的时候还是需要使用-lpthread选项的。

而C++11其实是有跨平台性的。因为它在不同平台下已经写好了不同版本的库。所以对我们而言,不同的平台写代码是没有感觉的。

我们最好使用C++的多线程。因为具有跨平台性

3.线程ID与进程地址空间布局

我们现在还没有解释这个tid究竟是什么东西,我们先看下面的代码

#include <iostream>
#include <pthread.h>
#include <string>
#include <cstdlib>
#include <unistd.h>
#include <thread>
using namespace std;

std::string toHex(pthread_t tid)
{
    char hex[64];
    snprintf(hex, sizeof(hex), "%p", tid);
    return hex;
}

void *threadRoutine(void *args)
{
    while(true)
    {
        cout << "thread id : " << toHex(pthread_self()) << endl; 
        sleep(1);
    }
}
int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRoutine, (void*)"thread 1");
    cout << "main thread create thread done, new thread id: " << toHex(tid) << endl;
    pthread_join(tid, nullptr);
    return 0;
}

运行结果为

image-20240229165932956

我们知道的是,内核中并没有明确的线程的概念,只有轻量级进程的概念

而轻量级进程接口是这样的

image-20240229170101484

这个接口我们一般是不用的,包括fork的底层其实用的也是这个接口

这个的第一个参数是一个函数指针,第二个参数是自定义的一个栈…

这个接口是被pthread线程库封装了。

所以我们采用的是pthread_create,pthread_join这些接口。

如下图所示,这个clone这个接口它需要提供一个回调函数,独立栈结构等,用它去维护线程。

而这些都是线程库在做的事情,也就是线程的概念是库给我们维护的,我们用的原生线程库,也要加载到内存中,因为都是基于内存的。线程库是一个动态库,经过页表映射后,也要到共享区的。

这些栈都是在共享区创建的。我们的线程库只需要维护线程的概念即可,不用维护线程的执行流,不过线程库注定了要维护多个线程属性集合,线程也要管理这些线程,先描述在组织。

而这个线程控制块它就要可以找到这些回调函数,独立栈,以及在内部的LWP。这个线程控制块就是用户级线程

image-20240229171544972

所以我们就将这个下面的这个叫做线程的tcb。而每一个tcb的起始地址,叫做线程的tid

image-20240229173206261

所以拿着这个tid,就可以找到库里面的属性了。

而我们前面打印出来的这个地址,我们也可以看到,它是比较大的,其实它就是介于堆栈之间的共享区

image-20240229173426579

每一个线程都必须要有自己的独立栈结构,因为它有独立的调用链,要进行压栈等操作。其中主线程用的就是地址空间中的这个栈。剩下的轻量级进程在我们创建的时候会先创建一个tcb,它里面的起始地址作为线程tid,它的里面有一个默认大小的空间,叫做线程栈,然后内核中调用clone创建好执行流。在clone中形成的临时数据都会压入到这个线程库中的栈结构中。

即,除了主线程,所有其他线程的独立站,都共享区,具体来讲是在pthread库中,tid指向的用户tcb中


所以其实Linux的线程 = 用户级线程 + 内核的LWP

线程可以分为用户级线程和内核级线程,而linux就属于用户级线程

在linux中, 每一个用户级线程就要对应内核中的一个LWP。用户级执行流:内核LWP = 1 : 1


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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/442717.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【❤️算法笔记❤️】-每日一刷-19、删除链表的倒数第 N个结点

文章目录 题目思路解答 题目 给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 输入&#xff1a;head [1,2,3,4,5], n 2 输出&#xff1a;[1,2,3,5]示例 2&#xff1a; 输入&#xff1a;head [1], n 1 输出&#xff1a;[]示例 3&…

接口自动化测试框架搭建:基于python+requests+pytest+allure实现

众所周知&#xff0c;目前市面上大部分的企业实施接口自动化最常用的有两种方式&#xff1a; 1、基于代码类的接口自动化&#xff0c;如&#xff1a; PythonRequestsPytestAllure报告定制 2、基于工具类的接口自动化&#xff0c;如&#xff1a; PostmanNewmanJenkinsGit/svnJme…

二分算法(蓝桥杯 C++ 题目 代码 注解)

目录 模板&#xff1a; 题目一&#xff08;分巧克力&#xff09;&#xff1a; 代码&#xff1a; 题目二&#xff08;M次方根&#xff09;&#xff1a; ​编辑代码&#xff1a; 题目三&#xff08;跳石头&#xff09;&#xff1a; 代码&#xff1a; 题目四&#xff08;扫…

基于SpringBoot的CNKI数据精炼与展示

目 录 摘 要 I Abstract II 引 言 1 1 相关技术 3 1.1 SpringBoot框架 3 1.1.1 Spring框架介绍 3 1.1.2 SpringBoot框架介绍 3 1.2 MyBatis框架 4 1.3 Echarts框架 5 1.4 Bootstrap框架 5 1.5 JQuery技术 6 1.6 本章小结 6 2 系统分析 7 2.1 功能需求分析 7 2.1.1 门户模块需求…

2024最新多目标优化算法:多目标指数分布优化器MOEDO(提供MATLAB代码)

一、多目标指数分布优化器&#xff08;MOEDO&#xff09; 多目标指数分布优化算法&#xff08;Multi-objective exponential distribution optimizer &#xff0c;MOEDO&#xff09;由Kalita, K等人于2024年提出&#xff0c;其采用增强的精英非主导分类和拥挤距离机制。MOEDO集…

2024手把手教你FL Studio 20 for Mac v20.8.3 中文破解版 水果音乐制作软件

网上大部分都是Windows安装教程&#xff0c;今天给大家分享一个FL Studio 20 Mac版激活教程&#xff0c;废话不多说&#xff0c;首先上一个FL Studio 20激活成功的截图 FL Studio 20 for Mac 破解版是最容易上手的编曲工具之一&#xff0c;直观的用户操作界面&#xff0c;强大的…

基于Unity3D的AVG卡牌游戏设计与实现

目 录 摘 要 I Abstract II 引 言 1 1 相关技术 3 1.1 C# 3 1.2 Unity3D 3 1.3 UGUI 3 1.4 XML 4 1.5 原型设计模式 4 1.6 本章小结 4 2 系统分析 5 2.1 用户需求 5 2.2 功能需求 5 2.3 非功能需求 6 2.4 本章小结 6 3 系统设计 7 3.1 系统该要设计 7 3.2 系统详细设计 7 3.2.…

DFS(深度优先搜索)C++(Acwing)

代码&#xff1a; #include <iostream>using namespace std;const int N 10;int n; int path[N]; bool st[N];void dfs(int u) {if(u n){for(int i 0; i < n; i) printf("%d ", path[i]);puts("");return;}for(int i 1; i < n; i){if(!st…

启动项目报502怎么处理呢?

您好&#xff0c;我是码农飞哥&#xff08;wei158556&#xff09;&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f4aa;&#x1f3fb; 1. Python基础专栏&#xff0c;基础知识一网打尽&#xff0c;9.9元买不了吃亏&#xff0c;买不了上当。 Python从入门到精…

Qt自定义控件

自定义控件 目的&#xff1a;将多个控件或者窗口作为一个整体被多次复用。 操作方式 1.首先进行自定义的ui设计&#xff0c;以及对应的.h和.cpp文件 2.到要使用的UI界面上&#xff0c;从控件库中拖拽一个Widget控件 3.右键点击"提升为" 4.填写自定义实现的类名&…

IP地址定位技术的主要功能及应用

在互联网时代&#xff0c;IP地址定位技术成为了一项重要的技术&#xff0c;它通过分析用户的IP地址&#xff0c;确定用户的地理位置信息。IP地址定位技术不仅在网络安全、网络管理等领域有着重要的应用&#xff0c;也在商业、广告营销等领域发挥着重要作用。IP数据云将探讨IP地…

万用表数据导出变化曲线图——pycharm实现视频数据导出变化曲线图

万用表数据导出变化曲线图——pycharm实现视频数据导出变化曲线图 一、效果展示二、环境配置三、代码构思四、代码展示五、代码、python环境包链接 一、效果展示 图1.1 效果展示 &#xff08;左图&#xff1a;万用表视频截图&#xff1b;右图&#xff1a;表中数据变化曲线图&am…

SQL设计时增加说明列

后关闭sql Studio,然后打开注册表,注册表地址: 计算机\HKEY_CURRENT_USER\SOFTWARE\Microsoft\SQL Server Management Studio\18.0_IsoShell\DataProject 如有版本不同,红色内容有所变化,修改内容如下: SSVPropViewColumnsSQL70,SSVPropViewColumnsSQL80 全修改为 1,2,6,7…

OJ_二叉排序树

题干 C实现 循环双指针法(一个指向父亲&#xff0c;一个指向待插入结点) #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <queue> using namespace std;struct TreeNode {char data;TreeNode* left;TreeNode* right; };void InsertBST(TreeNode* …

CubeMX使用教程(6)——ADC模拟输出

本篇将利用CubeMX开发工具学习ADC&#xff08;模拟输出&#xff09;的使用 我们还是利用上一章的工程进行二次开发&#xff0c;这样方便 首先打开CubeMX进行相关配置 通过查看G431RBT6开发板有关模拟输出部分的原理图可知&#xff0c;模拟输出用到的IO口是PB15和PB12 接着我…

llama-index调用qwen大模型实现RAG

背景 llama-index在实现RAG方案的时候多是用的llama等英文大模型&#xff0c;对于国内的诸多模型案例较少&#xff0c;本次将使用qwen大模型实现llama-index的RAG方案。 环境配置 &#xff08;1&#xff09;pip包 llamaindex需要预装很多包&#xff0c;这里先把我成功的案例…

Linux 之六:系统性能监控和挂载

系统性能 Linux系统中&#xff0c;有许多命令用于监测和分析性能指标。以下是一些常用的Linux性能分析命令&#xff1a; top&#xff1a;实时查看并监控CPU、内存以及各个进程的资源占用情况。htop&#xff08;需要安装&#xff09;&#xff1a;一个增强版的 top 命令&#x…

P3768 简单的数学题(莫比乌斯反演+杜教筛)

题目&#xff1a;P3768 简单的数学题 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 思路&#xff1a; 代码&#xff1a; #define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<string> #include<cstring> #include<cmath> #include<cti…

Java基础面试题(day 01)

&#x1f4d1;前言 本文主要是【Java】——Java基础面试题的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是听风与他&#x1f947; ☁️博客首页&#xff1a;CSDN主页听风与他 &#x1f304;每日一句&am…

【论文笔记】Mamba: Linear-Time Sequence Modeling with Selective State Spaces

原文链接&#xff1a;https://arxiv.org/abs/2312.00752 1. 引言 基石模型&#xff08;FM&#xff09;的主干网络通常是序列模型&#xff0c;处理任意的输入序列。但现代FM主要基于Transformer这一序列模型&#xff0c;及其核心的注意力。但是&#xff0c;自注意力仅能在上下…