Linux操作系统6- 线程2(线程的创建,终止,等待与退出)

发布于:2025-03-16 ⋅ 阅读:(17) ⋅ 点赞:(0)

上篇文章:Linux操作系统6- 线程1(线程基础,调用接口,线程优缺点)-CSDN博客

本篇Gitee仓库:myLerningCode/l28 · 橘子真甜/Linux操作系统与网络编程学习 - 码云 - 开源中国 (gitee.com)

        进程:承担操作系统分配资源的基本单位

        线程:CPU调度的基本单位,是进程内的执行流。在Linux中,使用轻量级进程模拟线程

本文的线程操作都是POSIX线程控制库。链接这些线程库需要在编译的时候加上 -lpthread

目录

一. 线程创建pthread_create 

二. 线程的终止

        2.1 pthread_exit

三. 线程的等待与退出

3.1 pthread_join

3.2 线程退出信息 


一. 线程创建pthread_create 

        调用接口原型

//所需头文件
#include <pthread.h>

//用于创建一个线程并执行对应函数
int pthread_create(pthread_t* thread, const pthread_attr_t *attr, void*(*start_rountine)(void*), void* args);

// thread    输入输出型参数,表示创造线程的tid

// attr      用于控制线程的属性,一般设置为nullptr

// start_rountine    一个函数指针,表示要线程执行的函数

// args             一个void*参数,线程创建后,将这个参数传递给start_routine的参数列表

//成功返回0,失败返回-1,并且设置错误码

        args参数是void*类型的,所以我们能够传递任意参数让其接收。

测试代码:

        我们使用vector保存线程的tid,同时创建一批线程,并且通过arg参数传递一个string类型作为线程的名字

#include <iostream>
#include <vector>
#include <unistd.h>
#include <pthread.h>
int gnum = 5;

void *start_routine(void *args)
{
    std::string name = static_cast<const char *>(args);
    while (true)
    {
        std::cout << "我是新线程,我的名字是" << name << "pid为:" << getpid() << std::endl;
        sleep(1);
    }
}

int main()
{
    std::vector<pthread_t> tids(gnum);
    for (int i = 0; i < tids.size(); ++i)
    {
        std::string name = "thread" + std::to_string(i);
        pthread_create(&tids[i], nullptr, start_routine, (void *)name.c_str());
    }

    while (true)
    {
        std::cout << "我是主线程,我的pid为:" << getpid() << std::endl;
        sleep(1);
    }
    return 0;
}

        测试结果如下:

           可以看到,我们创建了4个新线程,它们的pid相同。但是它们的名字为何没有0,2和3呢? 这是因为我们的name是同一个name,销毁后,线程仍访问这块销毁空间。

        所以这里我们需要定义一个结构体来保存数据。

示例代码:

#include <iostream>
#include <vector>
#include <unistd.h>
#include <pthread.h>
int gnum = 5;

struct ThreadData
{
    pthread_t tid;
    std::string name;
};

void *start_routine(void *args)
{
    ThreadData *td = static_cast<ThreadData *>(args);
    while (true)
    {
        std::cout << "我是新线程,我的名字是:" << td->name << " pid为:" << getpid() << std::endl;
        sleep(1);
    }
}

int main()
{
    std::vector<ThreadData *> tids(gnum);
    for (int i = 0; i < tids.size(); ++i)
    {
        // 在堆中创建数据
        ThreadData *td = new ThreadData();
        td->name = "Thread " + std::to_string(i);
        pthread_create(&td->tid, nullptr, start_routine, (void *)td);
        tids[i] = td;
    }

    while (true)
    {
        std::cout << "我是主线程,我的pid为:" << getpid() << " " << std::endl;
        sleep(1);
    }
    return 0;
}

测试结果如下:

        如果在线程的执行流中定义变量,这些变量是线程独有的 

修改start_routine

void *start_routine(void *args)
{
    int cnt = 10;
    ThreadData *td = static_cast<ThreadData *>(args);
    while (true)
    {
        std::cout << "我是新线程,我的名字是:" << td->name << "cnt:" << cnt-- << "cnt 地址为:" << &cnt << std::endl;
        sleep(1);
    }
}

        运行结果如下:可以看到,每一个线程中的cnt的地址都是不一样的。这说明每一个线程都有自己独立的栈资源

二. 线程的终止

        如果线程调用exit()会导致整个线程终止。

测试代码如下:在cnt为5的时候,线程调用exit进行终止

#include <iostream>
#include <vector>
#include <unistd.h>
#include <pthread.h>
int gnum = 5;

struct ThreadData
{
    pthread_t tid;
    std::string name;
};

void *start_routine(void *args)
{
    int cnt = 10;
    ThreadData *td = static_cast<ThreadData *>(args);
    while (true)
    {
        if (cnt == 5)
            exit(1);
        std::cout << "我是新线程,我的名字是:" << td->name << " cnt:" << cnt-- << " cnt 地址为:" << &cnt << std::endl;
        sleep(1);
    }
}

int main()
{
    std::vector<ThreadData *> tids(gnum);
    for (int i = 0; i < tids.size(); ++i)
    {
        // 在堆中创建数据
        ThreadData *td = new ThreadData();
        td->name = "Thread " + std::to_string(i);
        pthread_create(&td->tid, nullptr, start_routine, (void *)td);
        tids[i] = td;
    }

    while (true)
    {
        std::cout << "我是主线程,我的pid为:" << getpid() << " " << std::endl;
        sleep(1);
    }
    return 0;
}

运行结果如下:

        可以看到,线程调用exit会导致整个进程退出。exit是用于进程终止,而不是用于某一个线程的终止。

        线程终止是当线程的函数执行完毕就会自动退出。

修改start_routine:当cnt为0的时候,退出循环。以返回的形式终止线程

void *start_routine(void *args)
{
    int cnt = 10;
    ThreadData *td = static_cast<ThreadData *>(args);
    while (cnt--)
    {
        std::cout << "我是新线程,我的名字是:" << td->name << " cnt:" << cnt << " cnt 地址为:" << &cnt << std::endl;
        sleep(1);
    }
    return nullptr;
}

 运行结果如下:

        2.1 pthread_exit

可以通过pthread_exit退出线程。效果与return是一样的

//函数原型
void pthread_exit(void* retval)

        pthread_exit的效果与函数内使用return的效果是一样的,这里就不过多解释

三. 线程的等待与退出

        与进程等待一样,线程也需要等待。如果不等待线程,线程对应的pcb可能不会被释放。就会造成类似僵尸进程一样的资源泄露。        

        线程等待的作用

1 等待线程退出的资源与信息(通过 return 的返回值获取)

2 回收线程的pcb与内核资源防止资源泄露

        当然我们可以不获取线程退出的信息,但是必须收回线程的资源

3.1 pthread_join

        pthread_join用于主线程等待新线程的退出。通过输入输出型参数,可以获取主线程退出的信息和需要传递给主线程的资源。同时会回收新线程的pcb和内核资源。

//所需头文件
#include <pthread.h>

//函数原型
int pthread_join(pthread_t thread, void** retval)

//参数说明
thread:需要等待的线程
retval:输入输出型参数,通过指针的方法来获取我们的退出值

测试代码:主线程一次性等待所有的新线程

#include <iostream>
#include <vector>
#include <unistd.h>
#include <pthread.h>
int gnum = 5;

struct ThreadData
{
    pthread_t tid;
    std::string name;
};

void *start_routine(void *args)
{
    int cnt = 10;
    ThreadData *td = static_cast<ThreadData *>(args);
    while (cnt--)
    {
        std::cout << "我是新线程,我的名字是:" << td->name << " cnt:" << cnt << " cnt 地址为:" << &cnt << std::endl;
        sleep(1);
    }
    return nullptr;
}

int main()
{
    std::vector<ThreadData *> tds(gnum);
    for (int i = 0; i < tds.size(); ++i)
    {
        // 在堆中创建数据
        ThreadData *td = new ThreadData();
        td->name = "Thread " + std::to_string(i);
        pthread_create(&td->tid, nullptr, start_routine, (void *)td);
        tds[i] = td;
    }

    for (const auto &td : tds)
    {
        int n = pthread_join(td->tid, nullptr); // 使用bullptr表示不关心线程退出的状态与信息
        if (n != 0)
            std::cout << "线程退出失败" << std::endl;
        std::cout << "主线程成功等待新线程:" << td->name << std::endl;
    }

    while (true)
    {
        std::cout << "我是主线程,我的pid为:" << getpid() << " " << std::endl;
        sleep(1);
    }
    return 0;
}

测试结果如下:可以看到,主线程成功等待了所有等待新线程 

 

3.2 线程退出信息 

        无论是return还是调用pthread_join退出的信息都是 void*retval。pthread_join通过输入输出型参数 voidd**retval来获取退出信息。

测试代码如下:

#include <iostream>
#include <vector>
#include <unistd.h>
#include <pthread.h>
int gnum = 5;

struct ThreadData
{
    pthread_t tid;
    std::string name;
};

struct ThreadReturn
{
    int exit_code;
    int exit_result;
};

void *start_routine(void *args)
{
    int cnt = 10;
    ThreadData *td = static_cast<ThreadData *>(args);
    while (cnt--)
    {
        std::cout << "我是新线程,我的名字是:" << td->name << " cnt:" << cnt << " cnt 地址为:" << &cnt << std::endl;
        sleep(1);
    }
    ThreadReturn *tr = new ThreadReturn(); // 堆中创建的数据才能正常返回
    tr->exit_code = 0;
    tr->exit_result = td->tid;
    return (void *)tr; // 退出线程的信息结构体
}

int main()
{
    std::vector<ThreadData *> tds(gnum);
    for (int i = 0; i < tds.size(); ++i)
    {
        // 在堆中创建数据
        ThreadData *td = new ThreadData();
        td->name = "Thread " + std::to_string(i);
        pthread_create(&td->tid, nullptr, start_routine, (void *)td);
        tds[i] = td;
    }

    for (const auto &td : tds)
    {
        ThreadReturn *tr;                            // 用于获取线程退出的信息
        int n = pthread_join(td->tid, (void **)&tr); // 使用bullptr表示不关心线程退出的状态与信息
        if (n != 0)
            std::cout << "线程退出失败" << std::endl;
        std::cout << "主线程成功等待新线程:" << td->name;
        std::cout << " 该线程退出大队信息为 " << "exitcode:" << tr->exit_code << " exit_result:" << tr->exit_result << std::endl;
    }

    while (true)
    {
        std::cout << "我是主线程,我的pid为:" << getpid() << " " << std::endl;
        sleep(1);
    }
    return 0;
}

 测试结果如下:

        

        在进程退出中,我们会关心进程的退出码和退出信号。

        而线程不用关心异常信号,因为一旦出现异常信号整个进程都会退出。