主线程是不是在阻塞线程上运行?

Posted

技术标签:

【中文标题】主线程是不是在阻塞线程上运行?【英文标题】:Does the main thread run on blocking threads?主线程是否在阻塞线程上运行? 【发布时间】:2017-03-30 12:37:18 【问题描述】:

当我开始研究 c++11 中的线程时,我认为.join() 用于在主线程上进行阻塞操作,std::async() 用于运行非阻塞线程。

在我看来,这个答案很好地解释了std:async()。 https://***.com/a/15035157/1770034

但我想更好地理解 join 方法。我发现了几个这样的例子:https://***.com/a/11229853/1770034 从主线程中只创建了 1 个线程。

然后我发现了这个https://solarianprogrammer.com/2011/12/16/cpp-11-thread-tutorial/

来自网站的代码片段:

#include <iostream>
#include <thread>

static const int num_threads = 10;

//This function will be called from a thread

void call_from_thread(int tid) 
    std::cout << "Launched by thread " << tid << std::endl;


int main() 
    std::thread t[num_threads];

    //Launch a group of threads
    for (int i = 0; i < num_threads; ++i) 
        t[i] = std::thread(call_from_thread, i);
    

    std::cout << "Launched from the main\n";

    //Join the threads with the main thread
    for (int i = 0; i < num_threads; ++i) 
        t[i].join();
    

    return 0;

我对这部分很好奇的部分就在这里:

//Join the threads with the main thread
for (int i = 0; i < num_threads; ++i) 
    t[i].join();

如果主线程停止等待直到.join()完成,循环如何运行以加入更多线程?它的工作原理很棒!但是,为什么它会起作用?

为什么会这样?这是我对其工作方式的印象。

    主线程加入第一个线程。 主线程一直等到第一个线程完成。 第一个线程完成。 主线程继续 for 循环并加入第二个线程。 主线程一直等到第二个线程完成。 ... ... ...

如果它在for循环中不断循环,主线程什么时候真正被阻塞等待?

编辑

例子:

//Join the threads with the main thread
for (int i = 0; i < num_threads; ++i) 
    t[i].join();
    // --- Perform some long operation here ---

什么时候进行长操作?

【问题讨论】:

您最后的编号列表可能具有误导性:加入线程意味着“等待它完成”,即每个步骤对 2 和 3、5 和 6 等实际上都是一个步。 “加入”的意思不是“我和她一起散步”这个词的意思,即我们一起散步。线程不会一起运行,但调用者会一直等到被调用者完成运行。这就像在终点线加入马拉松选手一样。 @Peter 所以如果调用者等到被调用者完成运行。为什么不等第一个跑完马拉松的人跑完,再派第二个跑起来跑马拉松呢? 因为如果你有 1000 名跑者参加马拉松比赛,那么这项赛事大约需要 3000 小时。如果他们都同时跑,那么当老乔叔叔终于在同一天天黑前穿过时,就结束了。这就是并发的全部概念,从字面上看...实际上您可能只有 2、4 或 8 条通道(在双核、四核或八核上),但您仍然完成了大约 50%、25 % 或 12.5% 的时间与您的建议相比。 @Peter 我添加了一个编辑示例,说明我关心的问题。 嗯,我正在读... 【参考方案1】:

当我发布这个问题时,我不明白多线程到底发生了什么。

即使阅读了几篇关于 C++ 主题的教程,仍然可能有点模糊到底发生了什么。所以这是对实际情况的解释。

为简单起见,让我们使用以下代码:

#include <thread>

void my_function()

     *** Some Code ***


int main()

    std::thread threads[10]; //create 10 std::thread objects

    for(int loop = 0; loop < 10; loop++)
    
        threads[loop] = std::thread(my_function);
    

    //Threads are executing on machine and main code is also running

    for(int loop = 0; loop < 10; loop++)
    
        threads[loop].join; //If threads[loop] is still running wait for it, otherwise keep moving forward.
    

    return 0;
 //all 10 thread objects (threads) are going out of scope and their destructors are being called

在 main 的顶部(在主线程中),我们在堆栈上为 10 个线程对象创建空间。在第一个 for 循环中,我们实际上创建了一个 std::thread,它将在单独的线程上调用 my_function

假设我们有一个四核处理器,一个线程一次只能在一个处理器上运行。 (完全是假设!)主线程当前正在使用其中一个处理器,而我们的 10 个线程中的一个将使用第二个。其余 9 个中的一个将使用第三个,其余 8 个中的一个将使用第四个。

for 循环之间,我们可以在主线程上执行我们想要的所有代码。 (即:主线程没有被阻塞)。接下来我们进入第二个for 循环。这个循环将通过并将每个线程告诉join

假设此时线程 1-9 已经执行,但我们仍在后台运行线程 10。当for 循环加入线程1 时。由于它已经完成,for 循环再次运行。这通过前 9 个线程继续发生。现在我们join 线程 10。它仍在运行,主线程现在将等待线程 10 完成。 (如果您需要在某个时间点完成代码才能继续前进,这很好。)

接下来我们有return 0; 和我们创建的std::threads 超出范围,因为它们是对象,所以调用了析构函数。由于所有线程都连接到主线程,因此不会抛出异常并且一切都很好地结束。

如果我们想,“嘿,我不在乎线程是否完成,这个程序就结束了。”当std::thread 析构函数被调用时,std::thread 对象会发现有问题并引发std::terminate

如果我们真的不在乎线程何时结束或是否结束。我们可以使用detach。这将允许它运行直到它完成它的任务。如果它在 main 结束时仍在运行,则线程将毫无例外地死亡(现代操作系统会随进程终止它。)

joinable 是什么?如果您没有 join 到调用者的线程或 detach 来自调用者(在我们的例子中是主线程)的线程,joinable 将为真。如果你想检查一个线程是否仍在运行,而不是join,直到它运行为止。您需要改用std::async。 (Can C++11 tell if std::thread is active?)

【讨论】:

以上是关于主线程是不是在阻塞线程上运行?的主要内容,如果未能解决你的问题,请参考以下文章

Android主线程阻塞WebView线程

子线程怎么不阻塞主线程

等待一段时间不阻塞主线程

GCD主队列是不是必须在主线程上执行?

从后台线程调用 startActivity 并且主线程被阻塞时,Activity 延迟启动

执行许多 UIKit 操作阻塞主线程