【线程概念和线程控制】(二)

简介: 【线程概念和线程控制】(二)

2.3 🍎线程退出🍎

🍋pthread_join🍋

但是其实上面的代码中还存在一个很严重的问题,我们在学习进程中知道,父进程会wait子进程,否则就可能造成了内存泄漏。线程也是一样的,已经退出的线程,其空间没有被释放,仍然在进程的地址空间内;创建新的线程不会复用刚才退出线程的地址空间。主线程必须要回收其他线程的资源,否则就会造成内存泄漏,那回收其他线程的接口是啥呢?

我们来看看官网对pthread_join的介绍:

e43a2354ce274e85bd5899fed5b70402.png功能:等待线程结束

原型:

int pthread_join(pthread_t thread, void **value_ptr);

参数:

thread:线程ID

value_ptr:它指向一个指针,后者指向线程的返回值

返回值:成功返回0;失败返回错误码

第一个参数比较好理解,那么第二个参数是一个二级指针,接受的是一个线程的返回值,那么我们知道创建线程的参数里面有一个函数指针,该函数指针的返回值为void*,而这个返回值就可以传递给join的第二个参数使用,比如我们看看下面的代码:

void *Run(void *args)
{
    const char *name = static_cast<char *>(args);
    cout << "thread1 is running" << endl;
    sleep(2);
    return (void *)11;
}
int main()
{
    pthread_t p1;
    pthread_create(&p1, nullptr, Run, nullptr);
    cout << "I am is main thread,is running" << endl;
    sleep(2);
    void* ret=nullptr;
    pthread_join(p1,&ret);
    cout<<"new pthread exit   "<<ret<<endl;
    return 0;
}

当我们运行时:b8fa0b89da3d4938aa58ee15761f803d.png

我们发现我们通过返回值返回的11被join给接收到了。

🍋pthread_exit🍋

除了使用return 这种方式,我们还可以使用哪种方式终止线程呢?我们还可以使用pthread_exit接口来处理:


a6d24f1af2d44bf9a31877c88d616624.png

功能:线程终止

原型:

void pthread_exit(void *value_ptr);

参数:

value_ptr:value_ptr不要指向一个局部变量。

返回值:无返回值,跟进程一样,线程结束的时候无法返回到它的调用者(自身)

当我们这样使用时:

35a009329b604d8b8840a1fff955bd0f.png

我们可以来观察下运行结果:

136cb61710b74b87bec143b54cce30c4.png

🍋pthread_cancel🍋

除了上面我们讲解的这两种方式外,我们还可以使用pthread_cancel取消一个执行中的线程:

功能:取消一个执行中的线程

原型:

int pthread_cancel(pthread_t thread);

参数:

thread:线程ID

返回值:成功返回0;失败返回错误码

假如我们想要取消自己呢?我们如何得到自己的pid,我们可以使用pthread_self():


27732ee7dfe84685b68d72602cffce5e.png

我们下面来看看线程取消的基本用法:

void *threadRun(void* args)
{
    const char*name = static_cast<const char *>(args);
    int cnt = 5;
    while(cnt)
    {
        cout << name << " is running: " << cnt-- << " obtain self id: " << pthread_self() << endl;
        sleep(1);
    }
    pthread_exit((void*)11); 
    // PTHREAD_CANCELED; #define PTHREAD_CANCELED ((void *) -1)
}
int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRun, (void*)"thread 1");
    sleep(3);
    pthread_cancel(tid);
    void *ret = nullptr;
    pthread_join(tid, &ret);
    cout << " new thread exit : " << (int64_t)ret << "quit thread: " << tid << endl;
    return 0;
}

当我们直接运行时:


a2925cfd0f544007921691f49e0d391c.png

不难发现程序3s后直接退出了,其实也很好理解,因为我们退出的是主线程,所以肯定会直接退出的。

所以我们可以总结线程退出有三种方式:

  • 1️⃣从线程函数return,这种方法对主线程不适用,从main函数return相当于调用exit。
  • 2️⃣线程可以调用pthread_ exit终止自己。
  • 3️⃣一个线程可以调用pthread_ cancel终止同一进程中的另一个线程。
  • 调用pthread_join函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下:
  • 1️⃣如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值。

2️⃣ 如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数PTHREAD_ CANCELED(-1)。

3️⃣ 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数。

4️⃣ 如果对thread线程的终止状态不感兴趣,可以传nullptr给value_ ptr参数。

2.4 🍎分离线程🍎

  • 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
  • 如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。
  • 所以此时我们可以使用pthread_detach:

e9e6b968b87e4706ac659404ff880e67.png

可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离:

pthread_detach(pthread_self());

joinable和分离是冲突的,一个线程不能既是joinable又是分离的。

我们来验证下:

void* Run(void* args)
{
    pthread_detach(pthread_self());
    const char* name=static_cast<char*> (args);
    cout<<name<<" is running"<<endl;
    return nullptr;
}
int main()
{
    pthread_t p1;
    pthread_create(&p1,nullptr,Run,(void*)"thread1");
    int ret=pthread_join(p1,nullptr);
    if(ret==0)
        cout<<"wait success"<<endl;
    else
        cout<<"wait fail "<<endl;
    return 0;
}

当我们运行时:


0f2bf6598555447eb9e2ec63264f6ebb.png

为什么是运行success呀?不是说joinable和分离是冲突的吗?按道理这里应该会join失败的呀。

这是由于执行时是先执行的join,此时线程还没有被分离,自然就能够join成功了,我们可以像下面这样写,就会join失败:

dce483a85926462386e6b5e69214e8ff.png


当我们再次运行时:


f9049e58aaff42f59c62124d2a13edd7.png

2.5 🍎理解线程独立栈🍎

首先我们来看看一张图:

cd6661b4f7074490861fe26a060bc7a7.png

通过之前动静态库的知识我们知道,pthread库是加载到共享区的,那么也就决定了进程中所有线程都是可以访问得到该库的。但是从上图我们看见了有一个主线程栈的空间,这个空间又是为谁准备的呢?

其实这个空间是为主线程准备的,我们之前讲过其余线程中的栈是相互独立的,而这个独立栈的空间就开辟在共享区中,也就是独立栈的空间其实是由库帮助我们开辟的。上图右边第一个struct_pthread又是什么鬼呢?这个是管理共享区中线程的一种数据结构,类似于进程中的PCB。至于什么是局部存储,我们可以来写一个程序看看:

int g_val=20;
void* Run(void* args)
{
     const char* name=static_cast<char*> (args);
     while(true)
     {
        cout<<"g_val:"<<g_val<<"&g_val:"<<&g_val<<endl;
        sleep(1);
     }
}
int main()
{
    pthread_t pids[5];
    for(int i=0;i<5;++i)
    {
        char* name=new char[32];
        snprintf(name,32,"pthread%d:",i+1);
        pthread_create(pids+i,nullptr,Run,name);
    }
    for(int i=0;i<5;++i)
    {
        pthread_join(pids[i],nullptr);
    }
    return 0;
}

当我们运行时:

0afcf81753ad4dbd91ddd819ef4a570d.png

这也符合我们的预期,因为全局变量是所有线程共享的,但是当我们在全局变量前加上了__pthread后:

9f33d6d16e0045e080ae48e8a3aade0b.png

当我们运行时:

83bbc7f810104b04bb201ab27302c494.png

我们惊奇的发现居然地址不一样了,这其实就是将g_val分别保存了一份在各自的独立栈中。至于为什么打印出来的数据无规律是因为多线程并发访问的问题,我们后面在详细讲解。


目录
相关文章
|
1月前
|
消息中间件 存储 算法
【软件设计师备考 专题 】操作系统的内核(中断控制)、进程、线程概念
【软件设计师备考 专题 】操作系统的内核(中断控制)、进程、线程概念
96 0
|
1月前
|
算法 Java 开发者
Java中的多线程编程:概念、实现与性能优化
【4月更文挑战第9天】在Java编程中,多线程是一种强大的工具,它允许开发者创建并发执行的程序,提高系统的响应性和吞吐量。本文将深入探讨Java多线程的核心概念,包括线程的生命周期、线程同步机制以及线程池的使用。接着,我们将展示如何通过继承Thread类和实现Runnable接口来创建线程,并讨论各自的优缺点。此外,文章还将介绍高级主题,如死锁的预防、避免和检测,以及如何使用并发集合和原子变量来提高多线程程序的性能和安全性。最后,我们将提供一些实用的性能优化技巧,帮助开发者编写出更高效、更稳定的多线程应用程序。
|
11天前
|
分布式计算 JavaScript 前端开发
多线程、多进程、协程的概念、区别与联系
多线程、多进程、协程的概念、区别与联系
21 1
|
30天前
|
Java
Java中的多线程编程:概念、实现与挑战
【5月更文挑战第30天】本文深入探讨了Java中的多线程编程,涵盖了多线程的基本概念、实现方法以及面临的挑战。通过对Java多线程编程的全面解析,帮助读者更好地理解多线程在Java中的应用,提高程序的性能和效率。
|
21天前
|
Java 调度 开发者
Java中的多线程编程:概念与实践
【6月更文挑战第7天】本文深入探讨了Java中多线程编程的核心概念,通过实例演示如何有效利用多线程提升程序性能。文章首先介绍了线程的基本定义及其在Java中的实现方式,随后详细讨论了线程的生命周期、状态转换以及同步机制。最后,通过一个实际案例展示了多线程在解决具体问题中的应用。
15 3
|
1月前
|
算法 调度
【操作系统】处理机调度的基本概念和三个层次、进程调度的时机和方式、调度器、闲逛线程
【操作系统】处理机调度的基本概念和三个层次、进程调度的时机和方式、调度器、闲逛线程
135 3
|
1月前
|
机器学习/深度学习 PyTorch 算法框架/工具
神经网络基本概念以及Pytorch实现,多线程编程面试题
神经网络基本概念以及Pytorch实现,多线程编程面试题
|
1月前
|
存储 调度
进程与线程(概念、并行、并发)
进程与线程(概念、并行、并发)
|
1月前
|
Java 调度
多线程的基本概念和实现方式,线程的调度,守护线程、礼让线程、插入线程
多线程的基本概念和实现方式,线程的调度,守护线程、礼让线程、插入线程
27 0
|
1月前
|
算法 Java API
Java中的多线程编程:概念、实现与挑战
【4月更文挑战第28天】 在现代软件开发中,多线程编程已成为提高应用性能和响应能力的关键工具。特别是在Java这种广泛使用的编程语言中,多线程不仅增加了程序的并发性,还提升了资源利用率和用户体验。然而,多线程编程也带来了设计复杂性、数据一致性和线程安全等一系列挑战。本文旨在探讨Java多线程的核心概念,展现其实现方式,并讨论在设计和开发过程中可能遇到的技术难题及其解决方案。