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

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

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分别保存了一份在各自的独立栈中。至于为什么打印出来的数据无规律是因为多线程并发访问的问题,我们后面在详细讲解。


目录
相关文章
|
3月前
|
Go 调度 开发者
[go 面试] 深入理解进程、线程和协程的概念及区别
[go 面试] 深入理解进程、线程和协程的概念及区别
|
5月前
|
分布式计算 JavaScript 前端开发
多线程、多进程、协程的概念、区别与联系
多线程、多进程、协程的概念、区别与联系
87 1
|
5月前
|
分布式计算 并行计算 安全
在Python Web开发中,Python的全局解释器锁(Global Interpreter Lock,简称GIL)是一个核心概念,它直接影响了Python程序在多线程环境下的执行效率和性能表现
【6月更文挑战第30天】Python的GIL是CPython中的全局锁,限制了多线程并行执行,尤其是在多核CPU上。GIL确保同一时间仅有一个线程执行Python字节码,导致CPU密集型任务时多线程无法充分利用多核,反而可能因上下文切换降低性能。然而,I/O密集型任务仍能受益于线程交替执行。为利用多核,开发者常选择多进程、异步IO或使用不受GIL限制的Python实现。在Web开发中,理解GIL对于优化并发性能至关重要。
61 0
|
6月前
|
Java
Java中的多线程编程:概念、实现与挑战
【5月更文挑战第30天】本文深入探讨了Java中的多线程编程,涵盖了多线程的基本概念、实现方法以及面临的挑战。通过对Java多线程编程的全面解析,帮助读者更好地理解多线程在Java中的应用,提高程序的性能和效率。
|
2月前
|
数据采集 消息中间件 并行计算
进程、线程与协程:并发执行的三种重要概念与应用
进程、线程与协程:并发执行的三种重要概念与应用
57 0
|
3月前
|
缓存 前端开发 JavaScript
一篇文章助你搞懂java中的线程概念!纯干货,快收藏!
【8月更文挑战第11天】一篇文章助你搞懂java中的线程概念!纯干货,快收藏!
36 0
一篇文章助你搞懂java中的线程概念!纯干货,快收藏!
|
4月前
|
Java 程序员 调度
Java中的多线程编程:概念、实现及性能优化
【5月更文挑战第85天】本文主要探讨了Java中的多线程编程,包括其基本概念、实现方式以及如何进行性能优化。首先,我们将介绍多线程的基本概念,然后详细讨论如何在Java中实现多线程,包括继承Thread类和实现Runnable接口两种方式。最后,我们将探讨一些提高多线程程序性能的策略,如使用线程池和减少同步开销等。
|
4月前
|
监控 Java 调度
Java面试题:描述Java线程池的概念、用途及常见的线程池类型。介绍一下Java中的线程池有哪些优缺点
Java面试题:描述Java线程池的概念、用途及常见的线程池类型。介绍一下Java中的线程池有哪些优缺点
72 1
|
4月前
|
缓存 Linux 编译器
【Linux】多线程——线程概念|进程VS线程|线程控制(下)
【Linux】多线程——线程概念|进程VS线程|线程控制(下)
66 0
|
4月前
|
存储 Linux 调度
【Linux】多线程——线程概念|进程VS线程|线程控制(上)
【Linux】多线程——线程概念|进程VS线程|线程控制(上)
69 0