Linux-多线程2 ——线程等待、线程异常、线程退出、线程取消和线程分离

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


一、线程间的全局变量共享

上节课我们讲到,线程是共享一个地址空间的,所以对于全局变量,多个线程访问的一定是同一个全局变量。

这里提出一个疑问,既然线程是共享一个地址空间,那么为什么多线程之间为什么不能访问别人的的局部变量呢,答案是没有它们的地址。 但是如果我们通过一个定义一个全局变量来保存别人的局部变量的地址,其实也是可以访问到别的线程的局部变量,不过我们不推荐这么做。

示例代码如下

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

int count = 0;

void *threadRun(void *args)
{
    while (1)
    {
        count++;
        sleep(1);
    }
    return nullptr;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRun, (void *)"new thread");
    while (1)
    {
        std::cout << "count : " << count << std::endl;
        sleep(1);
    }
    return 0;
}

在这里插入图片描述

__thread 修饰全局变量

如果我们想让每一个线程都私有一份各自的全局变量,各个线程不互相干扰,那么我们就可以使用__thread来修饰。

在这里插入图片描述

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

__thread int count = 0;

void *threadRun(void *args)
{
    while (1)
    {
        count++;
        sleep(1);
    }
    return nullptr;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRun, (void *)"new thread");
    while (1)
    {
        std::cout << "count : " << count << std::endl;
        sleep(1);
    }
    return 0;
}

二、线程等待

与进程等待类似,当主线程还在运行时,其他新线程已经完成了它们的工作,我们的主线程也要等待我们的新线程来进行资源回收,避免类似于进程僵尸的情况,造成内存泄露问题。

man 3 pthread_join
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
Compile and link with -pthread.

参数 pthread_t thread 是线程id,是该你需要等待的线程。
参数 void** retval 是一个输出型参数,用于接收新线程退出时的返回值。
返回值:成功返回0;失败返回错误码
在这里插入图片描述

示例代码如下

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

void *threadRun(void *args)
{
	//pthread_self()用于返回自己的tid
    printf("%s : %p\n", (char *)args, pthread_self());
    // 为更好观察,休息5s
    sleep(5);
    return (void *)10;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRun, (void *)"new thread");
	sleep(5);
    int ret;
    pthread_join(tid, (void**)&ret);
    std::cout << "main thread: " << ret << std::endl;
    sleep(5);
    return 0;
}

pthread_self和tid

在上面的示例代码中,我们使用了pthread_slef函数用于返回该线程自身的线程id,可是为什么运行结果输出的并不是我们使用ps -aL命令所显示的LWP呢?

这是因为,我们所使用的pthread属于第三方库,在linux中又叫做原生线程库,对于多线程,我们也需要先描述再组织,而描述者应该是pthread库,所以pthread库中才含有我们的多线程的数据结构,而我们又学习过进程地址空间的分布。

我们的动态库的地址是被加载到共享区的,而pthread_slef所返回的tid其实是被映射的struct pthread的起始地址。
在这里插入图片描述


回到我们的线程等待,我们的pthread_join并不能像进程等待一样有非阻塞选项,这就说明一旦我们开始使用pthread_join开始等待新线程,如果新线程没有退出,我们的主线程就要一直被阻塞。


三、线程异常

当多线程进程执行时,无论哪个线程出现了异常,整个进程都会一起崩溃。

示例代码

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

void *threadRun(void *args)
{
    printf("%s : %p\n", (char *)args, pthread_self());

    // 为更好观察,休息5s
    sleep(5);

    int a = 10;
    a /= 0;
    return (void *)10;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRun, (void *)"new thread");
	sleep(5);
    while (1);
    return 0;
}

在这里插入图片描述

四、线程退出

在多线程进程中,我们在代码中应该尽量不使用exit()退出,因为exit是进程退出,任何一个线程调用此函数,都会使整个进程退出!

而如果我们想线程退出而不是进程退出,我们可以使用pthread_exit。

man 3 pthread_exit
#include <pthread.h>
void pthread_exit(void *retval);

参数void* retval就是用于代替回调函数的返回值。

示例代码

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

void *threadRun(void *args)
{
    printf("%s : %p\n", (char *)args, pthread_self());

    // 为更好观察,休息5s
    sleep(5);

    pthread_exit((void*) 11);

    return (void *)10;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRun, (void *)"new thread");
	sleep(5);
    int ret;
    pthread_join(tid, (void**)&ret);
    std::cout << "main thread: " << ret << std::endl;
    sleep(5);
    return 0;
}

在这里插入图片描述

五、线程取消

man 3 pthread_cancel
#include <pthread.h>
int pthread_cancel(pthread_t thread);
Compile and link with -pthread.

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

调用此函数,可以取消tid为thread的线程。

需要注意的是如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数PTHREAD_
CANCELED。

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

void *threadRun(void *args)
{
    printf("%s : %p\n", (char *)args, pthread_self());

    // 为更好观察,休息5s
    sleep(5);

    // pthread_cancel(pthread_self());

    return (void *)10;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRun, (void *)"new thread");
    sleep(5);
    pthread_cancel(tid);

    int ret;
    pthread_join(tid, (void **)&ret);

    std::cout << "PTHREAD_CANCELED : " << (int)PTHREAD_CANCELED << std::endl;
    std::cout << "main thread: " << ret << std::endl;
    sleep(5);
    return 0;
}

在这里插入图片描述

六、线程分离

当我们并不关心新线程退出后的退出码,不想使用pthread_join来阻塞主线程,又不想让退出后的新线程因为没有被等待导致内存空间泄露的问题,我们就可以让新线程进行分离,分离后,子线程退出时将自动释放资源。

man 3 pthread_detach
#include <pthread.h>
int pthread_detach(pthread_t thread);
Compile and link with -pthread.

一般调用此函数都是在新线程,让新线程自己分离自己,不过也可以分离别人。

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

void *threadRun(void *args)
{
    pthread_detach(pthread_self());
    printf("%s : %p\n", (char *)args, pthread_self());
    sleep(5);
    return (void *)10;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, threadRun, (void *)"new thread");
    sleep(3);
    int ret;
    int n = pthread_join(tid, (void **)&ret);

    std::cout << "PTHREAD_CANCELED : " << (int)PTHREAD_CANCELED << std::endl;
    std::cout << "main thread: " \
              << "n : " << n << " error: " << strerror(n) << std::endl;
    sleep(5);
    return 0;
}

pthread_join失败
在这里插入图片描述

  • 25
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
在 C++ 中,我们可以使用线程库来实现多线程编程。线程的挂起、唤醒与终止是多线程编程中非常重要的一部分。 线程的挂起也称为线程的休眠,它可以让线程停止运行一段时间,等待某个条件满足后再继续运行。在 C++ 中,我们可以使用 std::this_thread::sleep_for() 函数来实现线程的挂起,该函数可以让当前线程挂起一段时间,例如: ```cpp #include <chrono> #include <thread> int main() { // 挂起当前线程 1 秒钟 std::this_thread::sleep_for(std::chrono::seconds(1)); return 0; } ``` 线程的唤醒可以通过条件变量来实现,条件变量是一种同步机制,用于在线程之间传递信号。在 C++ 中,我们可以使用 std::condition_variable 类来创建条件变量,然后使用 wait() 函数来挂起线程等待条件变量的信号,使用 notify_one() 函数来唤醒一个等待条件变量的线程,例如: ```cpp #include <condition_variable> #include <mutex> #include <thread> std::condition_variable cv; std::mutex mtx; bool ready = false; void worker_thread() { // 等待条件变量的信号 std::unique_lock<std::mutex> lock(mtx); cv.wait(lock, [](){ return ready; }); // 条件满足后继续执行 // ... } int main() { // 唤醒等待条件变量的线程 { std::lock_guard<std::mutex> lock(mtx); ready = true; } cv.notify_one(); return 0; } ``` 线程的终止可以使用 std::thread::join() 函数来实现,该函数可以让当前线程等待另一个线程执行完成后再继续执行,例如: ```cpp #include <thread> void worker_thread() { // ... } int main() { std::thread t(worker_thread); // 等待 worker_thread 执行完成 t.join(); return 0; } ``` 另外,线程的终止还可以使用 std::thread::detach() 函数来实现,该函数可以让当前线程与创建的线程分离,使得两个线程可以独立运行,例如: ```cpp #include <thread> void worker_thread() { // ... } int main() { std::thread t(worker_thread); // 分离线程,使得两个线程可以独立运行 t.detach(); return 0; } ``` 需要注意的是,分离线程后,主线程不能再使用 join() 函数等待线程执行完成,否则会导致程序崩溃。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

风君子吖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值