SIGABRT для пользовательской фоновой задачи с использованием std :: thread и std :: condition_variable в C ++ - PullRequest
1 голос
/ 29 февраля 2020

Я написал небольшой класс async_task, который поддерживает поток в тепле, чтобы я мог выполнять фоновые вычисления. Задача может быть запущена несколькими потоками, но только один экземпляр задачи должен быть запущен одновременно. На одном из моих серверов CI (очень старый и медленный ma c mini около 2011 года - процессор Intel Penryn) мой модульный тест иногда не проходит с SIGABRT (компилируется с Clang 9.0 - не AppleClang - на macOS 10.13). На сборочной машине Windows 10 - процессоре Intel i9 никогда не происходит сбой.

Вот минимальное представление тестируемого кода и модульного теста, извлеченного в отдельное приложение C ++:

#include <thread>
#include <condition_variable>
#include <mutex>
#include <atomic>
#include <functional>
#include <iostream>

// class under test...

class async_task final
{
    std::thread thread_;
    std::condition_variable run_request_;
    std::mutex run_request_mutex_;
    std::atomic<bool> quit_{ false };
    std::atomic<bool> run_{ false };

public:
    async_task(std::function<void()> task) :
        thread_{ [this, task] { thread_proc(task); }}
    {
    }

    ~async_task()
    {
        {
            std::unique_lock<std::mutex> lock(run_request_mutex_);

            quit_ = true;
        }

        run_request_.notify_one();

        thread_.join();
    }

    void run()
    {
        {
            std::unique_lock<std::mutex> lock(run_request_mutex_);

            run_ = true;
        }

        run_request_.notify_one();
    }

private:
    void thread_proc(std::function<void()> task)
    {
        while (!quit_)
        {
            {
                std::unique_lock<std::mutex> lock{ run_request_mutex_ };

                run_request_.wait(lock, [this] { return quit_ || run_; });
            }

            bool run = false;

            if (run_.exchange(false))
            {
                run = !quit_;
            }

            if (run)
            {
                task();
            }
        }
    }
};


// exercising code...

int main()
{
    std::condition_variable condition;
    std::mutex mutex;

    std::atomic<bool> value = false;

    async_task task{ [&value, &mutex, &condition]()
    {
        {
            std::unique_lock<std::mutex> lock(mutex);

            value = true;
        }

        condition.notify_one();
    } };

    task.run();

    {
        using namespace std::chrono_literals;

        std::unique_lock<std::mutex> lock(mutex);

        if (!value)
        {
            condition.wait_for(lock, 5s, [&value] { return value.load(); });
        }
    }

    return EXIT_SUCCESS;
}

Должно быть состояние гонки, но я не могу на всю жизнь определить, что может вызвать SIGABRT. Кто-нибудь может определить проблему?

ОБНОВЛЕНИЕ: добавлен мьютекс в деструктор для защиты quit_, поскольку это было указано как вторичная проблема - хотя и не является причиной рассматриваемой проблемы.

1 Ответ

1 голос
/ 29 февраля 2020

Гонка очевидная происходит в порядке инициализации члена: std::thread запускается немедленно, и порожденный поток может получить доступ к мьютексу и переменной условия, прежде чем он будет фактически создан. Создание std::thread последним членом вашего класса должно исправить это.

...