Linux —— 线程控制

Linux —— 线程控制

  • 创建多个线程
  • 线程的优缺点
      • 优点
      • 缺点
  • pthread_self
  • 进程和线程的关系
    • pthread_exit
  • 线程等待pthread_ join
  • 线程的返回值
  • 线程分离
    • pthread_detach
  • 线程取消
    • pthread_cancel
  • pthread_t 的理解

我们今天接着来学习线程:

创建多个线程

我们可以结合以前的知识,创建多个线程:

#include <iostream>
#include <unistd.h>
#include <cstring>
#include <vector>
#include <functional>
#include <time.h>
#include <pthread.h>

// 使用std::function作为函数包装器类型别名
using func_t = std::function<void()>;

// 声明线程池中线程的数量
const int thread_num = 5;

// 定义一个结构体来保存线程的相关数据
class ThreadData {
public:
    // 构造函数初始化线程名称、创建时间和函数执行体
    ThreadData(const std::string &name, const uint64_t &ctime, func_t f)
        : thread_name(name), create_time(ctime), func(f) {}

    // 线程名称
    std::string thread_name;
    // 创建时间(以Unix时间戳表示)
    uint64_t create_time;
    // 线程执行的函数对象
    func_t func;
};

// 线程处理函数,由pthread_create调用
void *Threadhandler(void *args) {
    // 强制类型转换从void*到ThreadData*
    ThreadData *td = static_cast<ThreadData *>(args);
    while (true) {
        // 打印线程信息并执行函数体
        std::cout << "new thread thread name: " << td->thread_name 
                  << " create time: " << td->create_time << std::endl;
        td->func();
        // 模拟工作并让线程休眠1秒
        sleep(1);
    }
    // 注意:此函数理论上不会返回,因为while(true),但C++要求非void*函数有返回值,这里返回nullptr以满足编译要求
    return nullptr;
}

// 示例函数,供线程执行
void Print() {
    std::cout << "This is a process" << std::endl;
}

int main() {
    // 用于存储所有创建的线程ID的向量
    std::vector<pthread_t> threads;

    // 循环创建指定数量的线程
    for(int i = 0; i < thread_num; i++) {
        // 生成线程名称
        std::string name = "thread " + std::to_string(i + 1);
        pthread_t tid; // 线程ID

        // 初始化每个线程的数据结构
        ThreadData *td = new ThreadData(name, (uint64_t)time(nullptr), Print);

        // 创建线程,传入处理函数和参数
        pthread_create(&tid, nullptr, Threadhandler, td);
        // 将创建的线程ID添加到向量中
        threads.push_back(tid);
        // 主线程休眠1秒,模拟间隔创建线程的效果
        sleep(1);
    }

    // 打印所有创建的线程ID
    std::cout << "thread ids: ";
    for(const auto &tid: threads) {
        std::cout << tid << ",";
    }
    std::cout << std::endl;

    // 主线程可以在这里执行其他操作或等待,这里为了简化直接结束
    // 实际应用中可能需要适当同步机制来管理这些线程的生命周期

    return 0;
}

在这里插入图片描述我们也可以写段代码,监视我们的线程:

while :; do ps -aL | head -1 && ps -aL | grep mocess; sleep 1; done

在这里插入图片描述

线程的优缺点

线程(Threads)是现代操作系统中一种重要的并发执行机制,允许程序内部的多个控制流并发执行。以下是线程的一些主要优缺点:

优点

  1. 提高资源利用率:线程可以在同一个进程中共享内存和资源,减少了上下文切换的开销,提高了系统的整体效率和资源利用率。
  2. 响应速度更快:多线程程序可以在等待某个任务(如I/O操作)完成的同时,处理其他任务,使得用户界面更加流畅,响应速度更快。
  3. 简化编程模型:对于一些复杂的并发任务,使用多线程可以简化编程模型,使程序设计更加直观。
  4. 灵活性和可扩展性:线程使得程序可以根据需要动态地分配工作,易于扩展以适应不同的负载需求。
  5. 并行处理:在多处理器或多核系统中,线程可以并行执行,充分利用硬件资源,显著提升程序的执行效率。

缺点

  1. 资源共享和数据一致性问题:多个线程访问共享资源可能导致竞态条件、死锁和资源争用等问题,需要复杂的同步机制(如互斥锁、信号量等)来保证数据的一致性,这会增加编程复杂度。
  2. 上下文切换开销:尽管线程间上下文切换比进程快,但频繁的线程切换仍然会消耗CPU时间,降低性能。
  3. 内存和资源占用:每个线程都会占用一定的内存空间(如栈空间),大量线程会导致内存消耗增加,特别是在内存受限的环境中。
  4. 调试困难:多线程程序的调试相对单线程程序更为复杂,因为问题可能与线程的执行顺序有关,难以复现和定位错误。
  5. 死锁和活锁:不当的线程同步可能导致死锁,即两个或更多的线程互相等待对方释放资源而无法继续执行。此外,活锁也是可能的问题,线程持续进行无意义的操作等待某种条件发生,但实际上条件永远无法达成。

同时,线程的健壮性并不是很优秀:

线程的健壮性相比多进程来说通常被认为较低。这是因为线程共享同一进程的内存空间和资源,这种资源共享的特性带来了以下几点关于健壮性的影响:

  1. 资源共享风险:线程之间可以直接访问共享内存,包括全局变量和其他静态数据,这可能导致数据竞争和竞态条件。如果不采取合适的同步措施(如互斥锁、信号量等),一个线程对共享数据的修改可能会干扰其他线程,从而引发不可预测的行为,甚至程序崩溃。
  2. 异常传播:在某些情况下,一个线程的异常终止(如 segmentation fault)可能会直接影响到整个进程,导致所有线程一起终止。这是因为所有线程共享同一地址空间,一个线程的错误操作可能破坏其他线程正在使用的数据结构或资源。
  3. 线程安全问题:编写线程安全的代码需要额外的努力,比如正确管理锁的使用、避免死锁和活锁等,这增加了开发的复杂度。如果线程间的交互没有正确处理,很容易引入难以发现和修复的错误。
  4. 调试挑战:多线程程序的调试比单线程程序更为复杂。由于线程执行的并发性和不确定性,错误可能不会稳定复现,且问题的原因可能隐藏在复杂的线程交互中,这使得调试和故障排查变得困难。

我们举个例子,我们故意触发异常:

#include <iostream>
#include <unistd.h>
#include <cstring>
#include <vector>
#include <functional>
#include <time.h>
#include <pthread.h>

// 使用std::function作为函数包装器类型别名
using func_t = std::function<void()>;

// 声明线程池中线程的数量
const int thread_num = 5;

// 定义一个结构体来保存线程的相关数据
class ThreadData {
public:
    // 构造函数初始化线程名称、创建时间和函数执行体
    ThreadData(const std::string &name, const uint64_t &ctime, func_t f)
        : thread_name(name), create_time(ctime), func(f) {}

    // 线程名称
    std::string thread_name;
    // 创建时间(以Unix时间戳表示)
    uint64_t create_time;
    // 线程执行的函数对象
    func_t func;
};

// 线程处理函数,由pthread_create调用
void *Threadhandler(void *args) {
    int a = 10;
    // 强制类型转换从void*到ThreadData*
    ThreadData *td = static_cast<ThreadData *>(args);
    while (true) 
    {
        // 打印线程信息并执行函数体
        std::cout << "new thread thread name: " << td->thread_name 
                  << " create time: " << td->create_time << std::endl;
        td->func();

        if(td->thread_name == "thread 1")
        {
            std::cout << td->thread_name << " alarm !!!!!" << std::endl;
            a /= 0; // 故意制作异常
        }
        // 模拟工作并让线程休眠1秒
        sleep(1);
    }
    // 注意:此函数理论上不会返回,因为while(true),但C++要求非void*函数有返回值,这里返回nullptr以满足编译要求
    return nullptr;
}

// 示例函数,供线程执行
void Print() {
    std::cout << "This is a process" << std::endl;
}

int main() {
    // 用于存储所有创建的线程ID的向量
    std::vector<pthread_t> threads;

    // 循环创建指定数量的线程
    for(int i = 0; i < thread_num; i++) {
        // 生成线程名称
        std::string name = "thread " + std::to_string(i + 1);
        pthread_t tid; // 线程ID

        // 初始化每个线程的数据结构
        ThreadData *td = new ThreadData(name, (uint64_t)time(nullptr), Print);

        // 创建线程,传入处理函数和参数
        pthread_create(&tid, nullptr, Threadhandler, td);
        // 将创建的线程ID添加到向量中
        threads.push_back(tid);
        // 主线程休眠1秒,模拟间隔创建线程的效果
        sleep(1);
    }

    // 打印所有创建的线程ID
    std::cout << "thread ids: ";
    for(const auto &tid: threads) {
        std::cout << tid << ",";
    }
    std::cout << std::endl;

    // 主线程可以在这里执行其他操作或等待,这里为了简化直接结束
    // 实际应用中可能需要适当同步机制来管理这些线程的生命周期

    return 0;
}

在这里插入图片描述我们这里1号线程触发异常,整个进程直接挂掉。

因此,虽然多线程可以提高程序的效率和响应性,但其健壮性依赖于开发者对并发控制和资源管理的精细设计。实践中,通常推荐使用高级并发工具、遵循最佳实践,并进行充分的测试来确保线程安全和提高程序的健壮性。

pthread_self

pthread_self可以获取自身的线程id:
在这里插入图片描述在这里插入图片描述

进程和线程的关系

一般来说,线程和进程的关系是,牵一发而动全身,比如:一个线程退出,不能直接exit退出

#include <iostream>
#include <unistd.h>
#include <cstring>
#include <vector>
#include <functional>
#include <time.h>
#include <pthread.h>

// 使用std::function作为函数包装器类型别名
using func_t = std::function<void()>;

// 声明线程池中线程的数量
const int thread_num = 5;

// 定义一个结构体来保存线程的相关数据
class ThreadData 
{
public:
    // 构造函数初始化线程名称、创建时间和函数执行体
    ThreadData(const std::string &name, const uint64_t &ctime, func_t f)
        : thread_name(name), create_time(ctime), func(f) {}

    // 线程名称
    std::string thread_name;
    // 创建时间(以Unix时间戳表示)
    uint64_t create_time;
    // 线程执行的函数对象
    func_t func;
};

// 线程处理函数,由pthread_create调用
void *Threadhandler(void *args) 
{
    int a = 10;
    // 强制类型转换从void*到ThreadData*
    ThreadData *td = static_cast<ThreadData *>(args);
   
    
    // 打印线程信息并执行函数体
    std::cout << "new thread thread name: " << td->thread_name 
                << " create time: " << td->create_time << std::endl;
    td->func();

    // if(td->thread_name == "thread 1")
    // {
    //     std::cout << td->thread_name << " alarm !!!!!" << std::endl;
    //     a /= 0; // 故意制作异常
    // }
    // 模拟工作并让线程休眠1秒
    sleep(1);
    
    // 注意:此函数理论上不会返回,因为while(true),但C++要求非void*函数有返回值,这里返回nullptr以满足编译要求
    exit(0); //exit返回
    //return nullptr;
}

// 示例函数,供线程执行
void Print() 
{
    std::cout << "This is a process" << std::endl;
}

int main() {
    // 用于存储所有创建的线程ID的向量
    std::vector<pthread_t> threads;

    // 循环创建指定数量的线程
    for(int i = 0; i < thread_num; i++) 
    {
        // 生成线程名称
        std::string name = "thread " + std::to_string(i + 1);
        pthread_t tid; // 线程ID

        // 初始化每个线程的数据结构
        ThreadData *td = new ThreadData(name, (uint64_t)time(nullptr), Print);

        // 创建线程,传入处理函数和参数
        pthread_create(&tid, nullptr, Threadhandler, td);
        // 将创建的线程ID添加到向量中
        threads.push_back(tid);
        // 主线程休眠1秒,模拟间隔创建线程的效果
        sleep(1);
    }

    // 打印所有创建的线程ID
    std::cout << "thread ids: ";
    for(const auto &tid: threads) 
    {
        std::cout << tid << ",";
    }
    std::cout << std::endl;

    // 主线程可以在这里执行其他操作或等待,这里为了简化直接结束
    // 实际应用中可能需要适当同步机制来管理这些线程的生命周期

    return 0;
}

在这里插入图片描述我们这里创建了5个线程,但是线程1 exit会直接导致整个进程退出,所以,如何优雅的实现线程的退出呢?一般返回nullptr即可:

#include <iostream>
#include <unistd.h>
#include <cstring>
#include <vector>
#include <functional>
#include <time.h>
#include <pthread.h>

// 使用std::function作为函数包装器类型别名
using func_t = std::function<void()>;

// 声明线程池中线程的数量
const int thread_num = 5;

// 定义一个结构体来保存线程的相关数据
class ThreadData 
{
public:
    // 构造函数初始化线程名称、创建时间和函数执行体
    ThreadData(const std::string &name, const uint64_t &ctime, func_t f)
        : thread_name(name), create_time(ctime), func(f) {}

    // 线程名称
    std::string thread_name;
    // 创建时间(以Unix时间戳表示)
    uint64_t create_time;
    // 线程执行的函数对象
    func_t func;
};

// 线程处理函数,由pthread_create调用
void *Threadhandler(void *args) 
{
    int a = 10;
    // 强制类型转换从void*到ThreadData*
    ThreadData *td = static_cast<ThreadData *>(args);
   
    
    // 打印线程信息并执行函数体
    std::cout << "new thread thread name: " << td->thread_name 
                << " create time: " << td->create_time << std::endl;
    td->func();

    // if(td->thread_name == "thread 1")
    // {
    //     std::cout << td->thread_name << " alarm !!!!!" << std::endl;
    //     a /= 0; // 故意制作异常
    // }
    // 模拟工作并让线程休眠1秒
    sleep(1);
    
    // 注意:此函数理论上不会返回,因为while(true),但C++要求非void*函数有返回值,这里返回nullptr以满足编译要求
    //exit(0); //exit返回
    return nullptr;
}

// 示例函数,供线程执行
void Print() 
{
    std::cout << "This is a process" << std::endl;
}

int main() {
    // 用于存储所有创建的线程ID的向量
    std::vector<pthread_t> threads;

    // 循环创建指定数量的线程
    for(int i = 0; i < thread_num; i++) 
    {
        // 生成线程名称
        std::string name = "thread " + std::to_string(i + 1);
        pthread_t tid; // 线程ID

        // 初始化每个线程的数据结构
        ThreadData *td = new ThreadData(name, (uint64_t)time(nullptr), Print);

        // 创建线程,传入处理函数和参数
        pthread_create(&tid, nullptr, Threadhandler, td);
        // 将创建的线程ID添加到向量中
        threads.push_back(tid);
        // 主线程休眠1秒,模拟间隔创建线程的效果
        sleep(1);
    }

    // 打印所有创建的线程ID
    std::cout << "thread ids: ";
    for(const auto &tid: threads) 
    {
        std::cout << tid << ",";
    }
    std::cout << std::endl;

    // 主线程可以在这里执行其他操作或等待,这里为了简化直接结束
    // 实际应用中可能需要适当同步机制来管理这些线程的生命周期

    return 0;
}

在这里插入图片描述同时,我们还可以用pthread_exit:

pthread_exit

在这里插入图片描述

#include <iostream>
#include <unistd.h>
#include <cstring>
#include <vector>
#include <functional>
#include <time.h>
#include <pthread.h>

// 使用std::function作为函数包装器类型别名
using func_t = std::function<void()>;

// 声明线程池中线程的数量
const int thread_num = 5;

// 定义一个结构体来保存线程的相关数据
class ThreadData 
{
public:
    // 构造函数初始化线程名称、创建时间和函数执行体
    ThreadData(const std::string &name, const uint64_t &ctime, func_t f)
        : thread_name(name), create_time(ctime), func(f) {}

    // 线程名称
    std::string thread_name;
    // 创建时间(以Unix时间戳表示)
    uint64_t create_time;
    // 线程执行的函数对象
    func_t func;
};

// 线程处理函数,由pthread_create调用
void *Threadhandler(void *args) 
{
    int a = 10;
    // 强制类型转换从void*到ThreadData*
    ThreadData *td = static_cast<ThreadData *>(args);
   
    
    // 打印线程信息并执行函数体
    std::cout << "new thread thread name: " << td->thread_name 
                << " create time: " << td->create_time << std::endl;
    td->func();

    // if(td->thread_name == "thread 1")
    // {
    //     std::cout << td->thread_name << " alarm !!!!!" << std::endl;
    //     a /= 0; // 故意制作异常
    // }
    // 模拟工作并让线程休眠1秒
    sleep(1);
    
    // 注意:此函数理论上不会返回,因为while(true),但C++要求非void*函数有返回值,这里返回nullptr以满足编译要求
    //exit(0); //exit返回
    //return nullptr;
    pthread_exit(nullptr);
}

// 示例函数,供线程执行
void Print() 
{
    std::cout << "This is a process" << std::endl;
}

int main() {
    // 用于存储所有创建的线程ID的向量
    std::vector<pthread_t> threads;

    // 循环创建指定数量的线程
    for(int i = 0; i < thread_num; i++) 
    {
        // 生成线程名称
        std::string name = "thread " + std::to_string(i + 1);
        pthread_t tid; // 线程ID

        // 初始化每个线程的数据结构
        ThreadData *td = new ThreadData(name, (uint64_t)time(nullptr), Print);

        // 创建线程,传入处理函数和参数
        pthread_create(&tid, nullptr, Threadhandler, td);
        // 将创建的线程ID添加到向量中
        threads.push_back(tid);
        // 主线程休眠1秒,模拟间隔创建线程的效果
        sleep(1);
    }

    // 打印所有创建的线程ID
    std::cout << "thread ids: ";
    for(const auto &tid: threads) 
    {
        std::cout << tid << ",";
    }
    std::cout << std::endl;

    // 主线程可以在这里执行其他操作或等待,这里为了简化直接结束
    // 实际应用中可能需要适当同步机制来管理这些线程的生命周期

    return 0;
}

在这里插入图片描述

线程等待pthread_ join

线程既然是进程的迷你版,肯定也会有跟进程相关的地方,比如我们的线程退出时也是需要被等待的,而我们所用的接口就是就是pthread_join:
在这里插入图片描述
我们来举个例子:

#include <iostream> // 标准输入输出库
#include <unistd.h> // 定义了 usleep 函数,用于延迟线程执行
#include <cstring> // 字符串操作函数库
#include <vector> // 动态数组容器
#include <functional> // 函数对象包装器库
#include <time.h> // 时间相关函数库
#include <pthread.h> // POSIX 线程库

// 线程处理函数
void *Threadhandler(void *args)
{
    usleep(1000); // 线程启动后暂停1毫秒

    // 将传入的void指针转换为std::string类型,作为线程名称
    std::string name = static_cast<const char*>(args);
    int cnt = 5; // 循环计数器

    // 输出线程信息并等待一秒,循环5次
    while(cnt--)
    {
        std::cout << "I am a Light process My LWP is :" << pthread_self() << std::endl; // 打印当前线程的轻量级进程ID(LWP)
        sleep(1); // 线程休眠1秒
    }

    return nullptr; // 线程结束,返回空指针
}

int main()
{
    pthread_t tid; // 定义线程ID变量

    // 创建新线程,传入线程处理函数、线程属性(nullptr表示使用默认属性)、入口参数和线程ID的地址
    pthread_create(&tid, nullptr, Threadhandler, (void *)"thread - 1");

    std::cout << "I am the main Light process My LWP is :" << pthread_self() << std::endl; // 打印主线程的LWP
    sleep(10); // 主线程休眠10秒,保证子线程有足够时间运行

    // 等待子线程tid结束,成功返回0,失败返回非零值。第二个参数接收线程的返回值,这里不需要所以传入nullptr
    int n = pthread_join(tid, nullptr);
    std::cout << "return value is :" << n << std::endl; // 打印pthread_join的返回值,表示是否成功加入线程

    return 0; // 主程序结束
}

在这里插入图片描述
这里注意一下,如果线程退出并没有被等待,会导致类似像僵尸进程这样的问题,但是这个不怎么容易观察。

线程的返回值

我们来看看pthread_join的手册:
在这里插入图片描述后面这个retval这个参数,在线程退出的时候,会把退出的结果放到retval中,意思就是这个retval是一个输出型参数,输入之后会把线程的退出结果带出来:

我们可以试一试:

#include <iostream>
#include <unistd.h>
#include <cstring>
#include <vector>
#include <functional>
#include <time.h>
#include <pthread.h>

void *Threadhandler(void *args)
{
    usleep(1000);

    std::string name = static_cast<const char*>(args);
    int cnt = 5;

    while(cnt--)
    {
        std::cout << "I am a Light process My LWP is :" << pthread_self() << std::endl;
        sleep(1);
    }

    return (void *)"thread-1";
}



int main()
{
    pthread_t tid;

    pthread_create(&tid,nullptr,Threadhandler,(void *)"thread - 1");

    std::cout << "I am the main Light process My LWP is :" << pthread_self() << std::endl;
    

    void *ret = nullptr;

    int n = pthread_join(tid,&ret);
    std::cout << "return value is :" << (const char*)ret << std::endl;
    
    
    return 0;
}

我们这里返回的是一个字符串,我们看看我们能否打印的出来:
在这里插入图片描述这里用pthread_exit也是可以的。

这里既然是void *说明我们是可以传递任何类型,我们也可以传递一个结构体回去:

#include <iostream>
#include <unistd.h>
#include <cstring>
#include <string>

// 自定义线程返回结构体,用于存储线程退出时的相关信息
class ThreadReturn
{
public:
    // 构造函数,初始化线程ID、信息和退出码
    ThreadReturn(pthread_t id, std::string info, int code)
        : _id(id), // 线程ID
          _info(info), // 线程退出时的信息
          _code(code) // 线程退出码
    {
    }

    // 数据成员
    pthread_t _id; // 线程的ID,在线程退出时记录
    std::string _info; // 线程退出时的描述信息
    int _code; // 线程退出的状态码
};

// 线程处理函数
void *Threadhandler(void *args)
{
    usleep(1000); // 暂停1毫秒

    std::string name = static_cast<const char*>(args); // 获取线程名称
    int cnt = 5; // 循环计数器

    // 循环输出线程信息并休眠,模拟工作过程
    while(cnt--)
    {
        std::cout << "I am a Light process My LWP is :" << pthread_self() << std::endl;
        sleep(1);
    }

    // 线程处理完毕,创建ThreadReturn实例以传递退出信息
    ThreadReturn* ret = new ThreadReturn(pthread_self(), name, 10);
    return ret; // 将ThreadReturn对象的地址作为线程的返回值
}

int main()
{
    pthread_t tid; // 主线程中定义线程ID

    // 创建新线程,传入处理函数、线程属性、参数和线程ID指针
    pthread_create(&tid, nullptr, Threadhandler, (void *)"thread - 1");

    std::cout << "I am the main Light process My LWP is :" << pthread_self() << std::endl; // 输出主线程ID

    void *ret = nullptr; // 定义一个void指针来接收线程的返回值
    int n = pthread_join(tid, &ret); // 等待线程tid结束,并获取其返回值到ret指针

    // 将ret指针转换为ThreadReturn对象指针,以便访问其中的数据
    ThreadReturn* r = static_cast<ThreadReturn*>(ret);
    if(r != nullptr) { // 确保转换成功
        std::cout << "return value is :" << "id :" << r->_id << std::endl;
        std::cout << "return value is :" << "info :" << r->_info << std::endl;
        std::cout << "return value is :" << "code :" << r->_code << std::endl;
        delete r; // 释放分配的内存
    }

    return 0; // 主程序结束
}

在这里插入图片描述

线程分离

我们之前通过实验看到了线程和线程之间的关联,线程退出之后要进行回收。

但其实,如果我们的主线程只想完成自己的任务,而并不想管其他的线程可不可以呢?答案是可以的,我们可以进行线程分离,使之主线程不管其他线程的死活:

pthread_detach

在这里插入图片描述
pthread_detach() 是POSIX线程库中的一个函数,用于改变指定线程的分离状态。当一个线程被“分离”(detached)时,它会在执行结束后自动被系统回收资源,而不需要其他线程调用pthread_join()来显式等待它结束。这对于那些不需要收集线程返回值或者不需要精确控制线程结束时间的场景非常有用。

函数原型如下:

int pthread_detach(pthread_t thread);
  • 参数
  • thread:要分离的线程的标识符(ID),即之前通过pthread_create()创建线程时返回的值。
  • 返回值
    • 成功时返回0。
    • 失败时返回非零的错误码。

功能

  • 如果调用成功,指定的线程将在终止时自动释放其资源,包括栈和线程描述符,而不需要其他线程的进一步干预。
  • 分离状态的线程不能被其他线程通过pthread_join()来等待和回收资源。

使用场景

  • 当线程执行的任务是独立的,不需要与其他线程同步或交换数据时。
  • 当你不需要关心线程的具体结束状态或返回值,只关心它执行完成即可。

注意事项

  • 一旦线程被分离,就不能再通过pthread_join()来等待它。
  • 如果你既想在线程结束时执行某些清理工作,又想让它自动回收资源,可以在线程函数的最后手动执行清理操作,然后再调用pthread_exit()显式终止线程,这样分离后也能确保资源被正确回收。
  • 调用pthread_detach()前应确保线程还在运行,不要对尚未创建或已经终止的线程调用此函数。
void *Threadhandler(void *args)
{
    usleep(1000);
  
    std::string name = static_cast<const char*>(args);

    std::cout << "I am a Light process My LWP is :" << pthread_self() << std::endl;

    return nullptr;
}



int main()
{
    pthread_t tid;

    pthread_create(&tid,nullptr,Threadhandler,(void *)"thread - 1");
    pthread_detach(tid);

    std::cout << "I am the main Light process My LWP is :" << pthread_self() << std::endl;
    

    void *ret = nullptr;
    int n = pthread_join(tid,&ret);

    std::cout << "return value is :" << "code :"<< (long long int)n << std::endl;
    
    
    return 0;
}

在这里插入图片描述我们的错误码设置为了22,表示一个无效的输入:

错误码22通常对应于EINVAL错误,即无效参数(Invalid argument)。这意味着传递给pthread_join的线程ID无效,或者线程已经终止并且不是加入(joinable)状态。由于线程已经被显式分离(通过pthread_detach(pthread_self())),它不再是可加入状态,因此尝试用pthread_join来等待这个线程就会收到EINVAL错误。

线程取消

pthread_cancel

在这里插入图片描述

pthread_cancel() 是POSIX线程库中的一个函数,用于请求取消(cancellation)指定的线程。这意味着请求线程(调用pthread_cancel的线程)向目标线程发送一个取消请求,目标线程在接收到这个请求后,根据其取消状态和取消类型,可以选择立即终止或在某个合适的时机终止执行。

函数原型如下:

int pthread_cancel(pthread_t thread);
  • 参数
  • thread:要被取消的线程的标识符(ID),即之前通过pthread_create()创建线程时返回的值。
  • 返回值
    • 成功时返回0。
    • 失败时返回非零的错误码,如ESRCH(没有这样的线程)。

功能

  • 发送一个取消请求给指定的线程。目标线程是否响应这个请求取决于其取消状态和取消类型。
  • 线程默认是不响应取消的(即取消点的插入是可选的),这意味着仅发出取消请求并不会立即停止线程,除非线程在某个取消点上主动检查取消请求状态。
  • 为了使线程能够响应取消,开发者需要在代码中适当的位置插入取消点,通常是通过调用某些库函数(如sleep(), pthread_testcancel())自动完成的,或者显式地调用pthread_testcancel()

使用场景

  • 当需要基于外部条件(如用户中断、错误处理等)提前结束线程的执行时。
  • 在长任务中提供取消机制,增强程序的灵活性和响应性。

注意事项

  • 线程可以在接到取消请求后执行清理工作,通过设置取消类型(PTHREAD_CANCEL_DEFERREDPTHREAD_CANCEL_ASYNCHRONOUS)来控制响应方式。
  • 即使取消请求被发送,线程也可能不会立即终止,除非它正在执行取消点或已经设置了立即响应取消的类型。
  • 取消线程应谨慎使用,特别是当线程持有锁或资源时,突然取消可能导致资源泄露或其他一致性问题,需要确保资源正确清理。
  • 成功发送取消请求并不意味着线程已经终止,需要通过其他机制(如共享变量、条件变量等)来确认线程是否已响应取消并完成清理。

比如我们可以这样:

void *Threadhandler(void *args)
{
    usleep(1000);
    
    std::string name = static_cast<const char*>(args);
    int cnt = 5;
    while(cnt--)
    {
        std::cout << "I am a Light process My LWP is :" << pthread_self() << std::endl;
        sleep(1);
    }

    //pthread_detach(pthread_self());

    return nullptr;
}

int main()
{
    pthread_t tid;

    pthread_create(&tid,nullptr,Threadhandler,(void *)"thread - 1");
    std::cout << "I am the main Light process My LWP is :" << pthread_self() << std::endl;
    
    //pthread_detach(tid);
    int n = pthread_cancel(tid);
    std::cout << "cancel value :" << strerror((int64_t)n) << std::endl;
   
    void *ret = nullptr;
    n = pthread_join(tid,&ret);

    std::cout << "return value is :" << "code :"<< strerror((long long int)n) << std::endl;
    
    return 0;
}

在这里插入图片描述
我们看到取消和等待都是成功了的,但是如果我们线程分离了呢?

void *Threadhandler(void *args)
{
    usleep(1000);
    
    std::string name = static_cast<const char*>(args);
    int cnt = 5;
    while(cnt--)
    {
        std::cout << "I am a Light process My LWP is :" << pthread_self() << std::endl;
        sleep(1);
    }

    pthread_detach(pthread_self());

    return nullptr;
}

int main()
{
    pthread_t tid;

    pthread_create(&tid,nullptr,Threadhandler,(void *)"thread - 1");
    std::cout << "I am the main Light process My LWP is :" << pthread_self() << std::endl;
    
    pthread_detach(tid);
    int n = pthread_cancel(tid);
    std::cout << "cancel value :" << strerror((int64_t)n) << std::endl;
   
    void *ret = nullptr;
    n = pthread_join(tid,&ret);

    std::cout << "return value is :" << "code :"<< strerror((long long int)n) << std::endl;
    
    return 0;
}

在这里插入图片描述
我们发现,线程分离之后,可以完成线程取消,但是不能完成线程等待。

pthread_t 的理解

我们之前打印过pthread_t 的编号:
在这里插入图片描述

这串数字换成十六进制,其实就是一个地址,每一个线程都有自己的地址。

那么这个地址到底是什么呢?

我们首先知道,Linux在系统上并没有提供关于线程的接口,管理线程的是它原生的pthread库:

那么,我们每创建一个线程,库都要组织管理它,所以最后库中就会存储的有每个线程的结构地址:
在这里插入图片描述
而我们打印的那一大串数字就是每个struct_pthread 的地址。

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

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

相关文章

【Linux】认识文件(四):文件系统,inode,以及软硬连接

【Linux】认识文件&#xff08;四&#xff09;&#xff1a;文件系统,inode,以及软硬连接 一.磁盘(仅了解)1.组成2.CHS寻址 二.抽象化磁盘(仅了解)三.文件系统1.什么是文件系统2.ext2文件系统的结构i.Date blocksii.Block Bitmapiii.inode1.inode Table2.inode Bitmap iiiii.GDT…

使用 Python 进行图像验证码识别训练及调用

目录 1、验证码识别原理1.1 Tensorflow 介绍1.2 Tensorflow 运行原理1.3 卷积神经网络 CNN&#xff08;Convolutional Neural Networks&#xff09; 2、验证码识别实现步骤2.1 安装第三方模块2.1.1 安装 TensorFlow 模块2.2.2 安装 cuda2.2.3 下载 cudnn 2.2 读取验证码样本形成…

[源码安装]

1 pangolin安装 在Linux上常用的一个3D绘图库是Pangolin&#xff0c;它是基于OpenGL完成的&#xff0c;它不但支持OpenGL的基本操作&#xff0c;还提供了一些GUI的功能。 1.1 版本&#xff1a; pangolin —— v0.6 libpng —— 16 eigen —— 3.4 使用libpng12遇到下面的问…

网络编程套接字(一) 【简单的Udp网络程序】

网络编程套接字<一> 理解源端口号和目的端口号PORT VS PID认识TCP协议和UDP协议网络字节序socket编程接口sockaddr结构简单的UDP网络程序服务端创建套接字服务端绑定运行服务器客户端创建套接字关于客户端的绑定问题启动客户端启动客户端本地测试INADDR_ANY 理解源端口号…

【技术分享】 OPC UA安全策略证书简述

那什么是OPC UA证书&#xff1f;用途是什么&#xff1f; 简单来说它是身份验证和权限识别。 OPC UA使用X.509证书标准&#xff0c;该标准定义了标准的公钥格式。建立UA会话的时候&#xff0c;客户端和服务器应用程序会协商一个安全通信通道。数字证书&#xff08;X.509&#x…

图片压缩工具,这三款软件简单好用!

在数字化时代&#xff0c;图片已成为我们生活和工作中不可或缺的一部分。无论是社交媒体上的分享&#xff0c;还是工作中的文件传输&#xff0c;图片都扮演着重要的角色。然而&#xff0c;随着图片质量的提高&#xff0c;其占用的存储空间也越来越大&#xff0c;这给我们的存储…

英语词汇-Obsoleted

英语词汇&#xff0d;Obsoleted Obsoleted 废弃的&#xff0c;不用的&#xff0c;过时的。 Automation has obsoleted many unskilled workers. 自动化技术&#xff0c;已淘汰了很多低技能工人。 微信公众号&#xff1a; 常青柏 淘宝店铺名&#xff1a; 漫乐之家、梅兰竹菊…

企业管理咨询公司不会选?一文带你避开“坑人”陷阱

近年来&#xff0c;企业管理咨询公司如雨后春笋般涌现&#xff0c;数量之多令人眼花缭乱。所以&#xff0c;面对这么多的企业管理咨询公司&#xff0c;企业该选谁&#xff1f;又该如何选择&#xff1f;本文将从以下几个方面为大家解析。 首先&#xff0c;我们要明确自己的需求和…

代码随想录 打卡day23,24,25

1 二叉搜索树的最小绝对差 注意审题&#xff0c;题目当值说到是一个二叉搜索树&#xff0c;因此我们只需进行中序遍历即可&#xff0c;然后得到一个有序数组之后进行编辑&#xff0c;统计出来最小差。 class solution{ private:vector<int> vec;void traversal(TreeNode…

namenode启动失败 org.apache.hadoop.hdfs.server.common.InconsistentFSStateException:

小白的Hadoop学习笔记 2024/5/14 18:26 文章目录 问题解决报错浅浅分析一下core-ste.xml 问题 namenode启动失败 读日志 安装目录下 vim /usr/local/hadoop/logs/hadoop-tangseng-namenode-hadoop102.log2024-05-14 00:22:46,262 ERROR org.apache.hadoop.hdfs.server.namen…

位图(c++)

文章目录 1.位图概念2.位图的实现3.应用&#xff08;解决整形存在或次数问题&#xff09;3.1存在问题3.2次数问题 5.搜索的方法对比&#xff1a; 1.位图概念 和哈希一样&#xff0c;都是一个表来记录某个元素的个数或者存在与否&#xff1b;不同的是哈希使用的计算机定义的完整…

Qt5 互动地图,实现无人机地面站效果

一、概述 本文主要通过Qt5opmapcontrol实现一个简单的无人机地面站效果。opmapcontrol是一个比较古老的QT开源地面站库&#xff0c;可选择谷歌地图&#xff0c;必应地图&#xff0c; 雅虎地图&#xff0c;GIS等。可直接使用源码&#xff0c;也可以编译生成库进行调用。实现效果…

技艺高超的魔法师:Java运算符

在Java编程的世界里&#xff0c;运算符是连接变量和表达式的关键纽带&#xff0c;它们使得程序能够执行计算、比较、赋值等一系列操作。 一&#xff0c;基本概念 1&#xff0c;运算符是什么&#xff1f; 运算符是操作变量的符号。 2&#xff0c;分类 Java中的主要运算符类…

openlayer实现ImageStatic扩展支持平铺Wrapx

地图平铺&#xff08;Tiling&#xff09;是地图服务中常见的技术&#xff0c;用于将大尺寸的地图数据分割成许多小块&#xff08;瓦片&#xff09;&#xff0c;便于高效加载和展示。这种技术特别适用于网络环境&#xff0c;因为它允许浏览器只加载当前视图窗口内所需的地图瓦片…

摸鱼大数据——Linux搭建大数据环境(集群免密码登录和安装Hadoop)二

集群设置免密登录 克隆node1虚拟机的前置条件&#xff1a;node1虚拟机存在且处于关闭状态 1.克隆出node2虚拟机 1.node1虚拟机: 右键 -> "管理" -> "克隆" 2.图形化弹窗中: "下一页"->"下一页"->选择"创建完整克隆&…

蓝鹏测控:扩大出口,勇拓海外市场

蓝鹏测控自2012年成立以来&#xff0c;始终专注于工业测量仪器的研发、生产与销售。公司坚持经验与创新并存&#xff0c;长期与华北电力大学、河北大学等多所知名院校深度合作&#xff0c;拥有一支技术力量雄厚的研发团队。经过多年的努力&#xff0c;蓝鹏测控已研发出多款具有…

使用C++实时读取串口数据(window使用已编译LibModbus库并用QT实现一个实时读取串口数据)

先看这篇文章&#xff0c;写得很详细: QT应用篇 四、window编译LibModbus库并用QT编写一个Modbus主机 手把手教学 编译好的LibModbus库可以在上面文章里下载&#xff0c;也可以在我的链接里下载&#xff1a; 为了在Qt Creator中创建新项目并嵌入上述C代码&#xff0c;请执行以…

算法-卡尔曼滤波之卡尔曼滤波的第二个方程:预测方程(状态外推方程)

在上一节中&#xff0c;使用了静态模型&#xff0c;我们推导出了卡尔曼滤波的状态更新方程&#xff0c;但是在实际情况下&#xff0c;系统都是动态&#xff0c;预测阶段&#xff0c;前后时刻的状态是改变的&#xff0c;此时我们引入预测方程&#xff0c;也叫状态外推方程&#…

冯喜运:5.14今日黄金原油涨跌走势分析操作建议

【黄金消息面分析】&#xff1a;本周黄金市场将迎来关键的美国通胀数据&#xff0c;包括周二的生产者价格指数(PPI)和周三的消费者物价指数(CPI)。这些数据对美联储的政策路径至关重要&#xff0c;可能会影响市场对利率调整的预期。目前&#xff0c;现货黄金价格小幅上涨&#…

Redis知识总结

文章目录 1. NoSQL2. Redis介绍3. Redis的下载与安装3.1 Windows版3.2 Linux版 4. Redis的数据类型5. Redis常用命令5.1 操作字符串的命令5.2 操作哈希结构的命令5.3 操作列表的命令5.4 操作set集合的命令5.5 操作zset集合的命令5.6 Redis通用命令5.7 其他命令 6. 在Java中操作…