进度条导致程序暂停和锁定,如何解决?

Progress Bar Causes Program to Halt and Lock, How Can I Fix It?

下面是我正在使用的当前代码。当我注释掉 运行 和 progress_bar 函数的代码时,代码按预期完美运行,并将 mandelbrot 打印到单独的图像文件中。然而,无论出于何种原因,当我尝试包含该函数时,它会在程序的其余部分完成时尖叫一切停止。如何在程序不锁定到伪死锁状态的情况下包含 progress_bar 函数?感谢您提供任何帮助。

#include <cstdint>
#include <cstdlib>
#include <complex>
#include <fstream>
#include <iostream>
#include <vector>
#include <thread>
#include <mutex>

// Import things we need from the standard library
using std::chrono::duration_cast;
using std::chrono::milliseconds;
using std::complex;
using std::cout;
using std::endl;
using std::ofstream;

// Define the alias "the_clock" for the clock type we're going to use.
typedef std::chrono::steady_clock the_clock;


// The size of the image to generate.
const int WIDTH = 1920;
const int HEIGHT = 1200;

// The number of times to iterate before we assume that a point isn't in the
// Mandelbrot set.
// (You may need to turn this up if you zoom further into the set.)
const int MAX_ITERATIONS = 500;

// The image data.
// Each pixel is represented as 0xRRGGBB.
uint32_t image[HEIGHT][WIDTH];

double progress;
bool progressDone = false;
std::mutex locking;
std::condition_variable conditionMet;
int partsDone = 0;

// Write the image to a TGA file with the given name.
// Format specification: http://www.gamers.org/dEngine/quake3/TGA.txt

void progress_bar() {

    std::unique_lock<std::mutex> lock(locking);
    while (!progressDone) {

        //std::this_thread::sleep_for(std::chrono::nanoseconds(100));
        cout << "Current Progress is at: " << progress << "%\n";
        conditionMet.wait(lock);

    }

    cout << "Mandelbrot is finished! Take a look.";

}

void write_tga(const char *filename)
{
    ofstream outfile(filename, ofstream::binary);

    uint8_t header[18] = {
        0, // no image ID
        0, // no colour map
        2, // uncompressed 24-bit image
        0, 0, 0, 0, 0, // empty colour map specification
        0, 0, // X origin
        0, 0, // Y origin
        WIDTH & 0xFF, (WIDTH >> 8) & 0xFF, // width
        HEIGHT & 0xFF, (HEIGHT >> 8) & 0xFF, // height
        24, // bits per pixel
        0, // image descriptor
    };
    outfile.write((const char *)header, 18);

    for (int y = 0; y < HEIGHT; ++y)
    {
        for (int x = 0; x < WIDTH; ++x)
        {
            uint8_t pixel[3] = {
                image[y][x] & 0xFF, // blue channel
                (image[y][x] >> 8) & 0xFF, // green channel
                (image[y][x] >> 16) & 0xFF, // red channel
            };
            outfile.write((const char *)pixel, 3);
        }
    }

    outfile.close();
    if (!outfile)
    {
        // An error has occurred at some point since we opened the file.
        cout << "Error writing to " << filename << endl;
        exit(1);
    }
}


// Render the Mandelbrot set into the image array.
// The parameters specify the region on the complex plane to plot.
void compute_mandelbrot(double left, double right, double top, double bottom, double start, double finish)
{
    for (int y = start; y < finish; ++y)
    {
        for (int x = 0; x < WIDTH; ++x)
        {
            // Work out the point in the complex plane that
            // corresponds to this pixel in the output image.
            complex<double> c(left + (x * (right - left) / WIDTH),
                top + (y * (bottom - top) / HEIGHT));

            // Start off z at (0, 0).
            complex<double> z(0.0, 0.0);

            // Iterate z = z^2 + c until z moves more than 2 units
            // away from (0, 0), or we've iterated too many times.
            int iterations = 0;
            while (abs(z) < 2.0 && iterations < MAX_ITERATIONS)
            {
                z = (z * z) + c;

                ++iterations;
            }

            if (iterations == MAX_ITERATIONS)
            {
                // z didn't escape from the circle.
                // This point is in the Mandelbrot set.
                image[y][x] = 0x000000; // black
            }
            else if (iterations == 0) {

                image[y][x] = 0xFFFFFF;

            }
            else
            {
                // z escaped within less than MAX_ITERATIONS
                // iterations. This point isn't in the set.
                image[y][x] = 0xFFFFFF; // white
                image[y][x] = 16711680 | iterations << 8 | iterations;
            }

            std::unique_lock<std::mutex> lock(locking);
            progress += double((1.0 / (WIDTH*HEIGHT)) * 100.0);
            conditionMet.notify_one();
        }
    }
    partsDone += 1;
}


int main(int argc, char *argv[])
{
    cout << "Please wait..." << endl;

    // Start timing
    std::vector<std::thread*> threads;
    the_clock::time_point start = the_clock::now();

    std::thread progressive(progress_bar);

    for (int slice = 0; slice < 2; slice++) {

        // This shows the whole set.
        threads.push_back(new std::thread(compute_mandelbrot, -2.0, 1.0, 1.125, -1.125, HEIGHT * (slice / 2), HEIGHT * ((slice + 1) / 2)));

        // This zooms in on an interesting bit of detail.
        //compute_mandelbrot(-0.751085, -0.734975, 0.118378, 0.134488, 0, HEIGHT/16);

    }

    // Stop timing

    for (std::thread* t : threads) {

        t->join();
        delete t;

    }

    if (partsDone == 2) {

        progressDone = true;

    }

    progressive.join();

    the_clock::time_point end = the_clock::now();

    // Compute the difference between the two times in milliseconds
    auto time_taken = duration_cast<milliseconds>(end - start).count();
    cout << "Computing the Mandelbrot set took " << time_taken << " ms." << endl;

    write_tga("output.tga");

    std::this_thread::sleep_for(milliseconds(3000));

    return 0;
}```

您 non-termination 的原因是 no-one 在所有工作完成后通知进度条线程。在调用 progressive.join() 之前添加 conditionMet.notify_one();。我在下面的 Demo 中省略了 IO 以便能够 运行 在在线编译器中。另外(正如@GoswinvonBrederlow 在评论中提到的那样)确保将 partsDone 变成 std::atomic 因为如果 >1 个线程调用 partsDone += 1 你最终会得到未定义的结果,反过来你会'无法判断您的程序是否已完成。

如果您将进度更改为 std::atomic 并且让您的进度打印机以 100 毫秒的间隔加载变量(并打印在上一行的顶部),这一切看起来会更简单。那么您所需要的只是 progressDone 标志,而不是为进度值的每次修改锁定和打印。您可以在下面的 Demo 中看到此 运行 具有零线程消毒剂警告。确保调整打印间隔。 此更改将 运行时间从 ~10.7 秒减少到 7 秒,尽管这只是一个指示 - 在线程清理器打开的情况下为您的程序计时是不合时宜的。