0
点赞
收藏
分享

微信扫一扫

【C/C++编程】深入C++ POD types

guanguans 2024-10-12 阅读 15

目录

pthread库

线程控制

线程创建    

pthread_create接口

实验

全局变量与多线程

多线程发生异常

​编辑 tid

线程等待

pthread_join接口

retval

线程的终止

pthread_exit接口

pthread_cancel接口

使用自定义类作为线程接收对象

线程库的底层原理 

线程栈


pthread库

        我们知道,  所有的线程, 都属于同一个进程所有的线程让他们去打印PID, 那么打印出来的PID最终会是同一个PID。 但是, 我们的线程如果被调度, 那么他就要有一个供别人调用的属于自己的ID。

        那么内核当中,有没有很明确的线程的概念呢? 没有, 内核当中只有一个“轻量级进程”的概念。 但是, 并不影响我们的每一个执行流(线程) 都有属于自己的ID, 这个ID叫做tid。 

        那么, 既然内核中只有“轻量级进程”的概念, 那么他是不是就不会给我们提供线程的系统调用, 只会给我们提供轻量级进程的系统调用!——但是我们用户要使用线程的创建方法, 所以linux程序员, 就在系统和用户层之间开发出了一个pthread线程库。 这个库是在应用层的。是对轻量级进程的相关接口进行封装, 为用户提供直接控制线程的接口。几乎所有的linux平台, 都是默认自带pthread库的!linux中编写多线程代码, 需要使用pthread库!

线程控制

线程创建    

pthread_create接口

实验

        下面我们使用一下这个接口, 创建一个新线程。 

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

using namespace std;


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

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

    return 0;
}

        那么既然创建的新线程去执行了新的函数, 那么就注定了这两个线程一个线程执行main函数, 访问的是main函数的代码;另一个线程执行threadRoutine函数, 访问的是threadRoutine函数的代码。 然后我们运行结果如下:

        上面这是链接式报错。 这是因为我们这里用的接口不是系统调用, 是库方法!!不是c/c++库, 是第三方库。 我们在学习动态库的时候学过, 这里必须我们自己指定链接哪一个库。 那么链接哪一个库呢? 起始man手册里面已经告诉我们了, 如下图:

        就是链接这个-pthread库。 我们在makefile中, 链接上这个库:

        然后就能编译成功了:

        运行后, 我们可以看到打印出来的PID是一样的:

        同时, 我们如果使用ps axj也能看到进程也只是一个:

        我们想要查到两个执行流, 怎么做呢? 这里有一个选项叫做ps -aL(a表示所有, L可以理解成light)

        图中我们画的这个LWP是什么东西呢? 其实, 在我们的linux中, 并没有真正意义上的线程, 是使用的进程模拟的线程。 cpu调度的时候, 不仅仅只看我们的PID, 每一个轻量级进程也有一个自己的标识符, 就是这个LWP(light weight process id)。所以, cpu在调度进程的时候根部看的不是PID, 看的是LWP!!!

        另外, 我们发送信号, 如果是给一个线程发, 那么同一个进程内的其他线程同样会挂掉。 就如同下图:

全局变量与多线程

        我们创建一个全局变量g_val, 让g_val在主线程进行加加操作。 然后主线程和父线程都对这个g_val进行打印。代码如下:



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

using namespace std;

int g_val = 100;

void* threadRoutine(void* args)
{
    while (true)
    {
        printf("new thread pid: %d, g_val: %d, &g_val: 0x%p\n", getpid(), g_val, &g_val);

        sleep(1); 


    }
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRoutine, nullptr);  

    while(true)
    {
        printf("main thread pid: %d, g_val: %d, &g_val: 0x%p\n", getpid(), g_val, &g_val);

        sleep(1);
        g_val++;

    }


    return 0;
}

运行后我们就会发现g_val会逐渐增大!!同时, 新线程也能够看到这个值会变化。 也就是说, 全局变量, 对于所有的线程来说是可见的!

多线程发生异常

        如果多线程发生了异常, 不管是哪一个执行流发生异常, 都会导致进程退出。 下面是测试代码(五秒后新线程发生除零错误, 异常, 进程退出):


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

using namespace std;

int g_val = 100;



void* threadRoutine(void* args)
{
    while (true)
    {
        printf("new thread pid: %d, g_val: %d, &g_val: 0x%p\n", getpid(), g_val, &g_val);

        sleep(5); 

        int a = 10;
        a /= 0;

        // cout << "new thread, pid: " << getpid() << endl; 
        // show("[new thread]");
    }
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRoutine, 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]");
        sleep(1);
        g_val++;

    }


    return 0;
}

运行结果: 

 tid

        在上面的实验里面, 我们没有打印过tid。 我们现在以十六进制打印一下这个tid。

        然后运行就能看到显然我们的tid和LWP是不一样的。 

         这是因为LWP是操作系统层面的概念, 作为操作系统自己知道即可, 我们用户并不关心LWP, 我们只关心tid。而这个tid是什么, 其实就是共享区的一块地址(涉及到了底层原理, 后面会讲到)

线程等待

pthread_join接口

而且我们等待线程和等待进程的目的类似, 有两个:

retval

        现在我们讨论一下这个第二个参数, 这个第二个参数其实就是拿到我们的新线程的返回值。 我们如果仔细观察就会发现, 我们的的我们的新线程是以回调函数的方式传给pthread_create的, 所以我们无法获得新线程的返回值。 而如果想要拿到这个返回值, 就要用到等待线程时的第二个参数, 这个参数的原理是什么? 下面讲解一下:

线程的终止

pthread_exit接口

        首先我们需要知道的是, exit是用来终止进程的, 不能用来终止线程。 如果使用exit终止线程, 会让我们的整个进程都退出。 

        线程库为我们提供了线程的退出方法:pthread_exit

参数类似于exit里面的参数。 就是退出码。 也类似于返回值。 

下面为简单的代码测试

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

void* threadRuntine(void* args)
{
    string name = static_cast<char*>(args);
    int cnt = 5;
    while (cnt--)
    {
        cout << args << " say#: " << "I am a new thread" << endl;
        sleep(1);
    }
    pthread_exit((void*)1);
}


int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRuntine, (void*)"thread 1");

    void* ret;
    pthread_join(tid, &ret);
    cout << (long long int)ret << endl;
    return 0;
}

pthread_cancel接口

        这个函数的作用是给新线程发送一个取消请求, 并且退出的线程, 退出码为-1。(注意, 不常用)

void* threadRuntine(void* args)
{
    string name = static_cast<char*>(args);
    int cnt = 5;
    while (cnt--)
    {
        cout << args << " say#: " << "I am a new thread" << endl;
        sleep(1);
    }
    pthread_exit((void*)1);
}


int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRuntine, (void*)"thread 1");

//两秒后就直接退出
    sleep(2);
    pthread_cancel(tid);

//
    void* ret;
    pthread_join(tid, &ret);
    cout << (long long int)ret << endl;
    return 0;
}

使用自定义类作为线程接收对象

我们可以使用自定义类型作为线程的接收对象。

        我们使用一下自定义类型的对象, 实验过程为: 首先定义两个类, 一个Request, 一个Response。 其中Request的成员变量有一个start, 一个end。 我们新线程就是来计算从start到end的总和。 然后就是Response, Response用来新线程的返回。里面的成员包含一个_result, 一个_exitcode。 


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


class Request
{
public:
    Request(int start, int end, 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* threadRuntine(void* args)
{
    Response* rsp = new Response(0, 0);
    Request* req = static_cast<Request*>(args);
    for (int i = req->_start; i <= req->_end; i++)
    {
        rsp->_result += i;
        usleep(100000);
        cout << ".exe is running..." << i << endl;
    }
    return (void*)rsp;
}

int main()
{
    Request* req = new Request(1, 100, "thread 1");
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRuntine, (void*)req);

    void* ret;

    pthread_join(tid, &ret);
    cout << "result: " << static_cast<Response*>(ret)->_result << endl;

    return 0;
}

运行结果:

线程库的底层原理 

        我们理解线程库的大概底层原理, 其实只要理解下面博主画的这张图即可:

线程栈

——————以上就是本节全部内容哦, 如果对友友们有帮助的话可以关注博主, 方便学习更多知识哦!!!  

举报

相关推荐

0 条评论