223 / 191 / 34
Регистрация: 19.02.2021
Сообщений: 1,387
1

Уперся в тупик с потоками. Прошу подсказки

30.09.2023, 14:04. Показов 1291. Ответов 50
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Здравствуйте!
Есть у меня небольшая самописная демка, писанная когда-то на Delphi. Решил ее переписать на C++ в рамках самообразования.
Демка простая - одни объекты гоняются за другими, я на ней тренировался выполнять параллельные расчеты. На Delphi все работало "на ура", а вот с C++ возникли проблемы.

Архитектура примерно такая:
1. Есть Контроллер, который отвечает за перемещение и взаимодействие объектов. Он крутится в отдельном потоке. Вернее, не он целиком, а только один из его методов. Возможно, в этом и проблема. Метод запускается через std::thread(&CController::Execute,m_pController).detach(). detach() потому то при join() все зависало.
2. Есть "Визуализатор", который отвечает за отображение объектов. Его процедуры, отвечающие за отрисовку, вызываются из Контроллера. "Визуализатор" является членом класса Контроллера по специальному интерфейсу.
3. Есть что-то типа пула вспомогательных потоков, (принадлежат классу Контроллера), который занимаются непосредственно расчетами.

В "Визуализаторе" предусмотрен диалог с настройками, по выходу из которого старый Контроллер уничтожается и создается новый экземпляр с новыми настройками.
И вот у меня не получается корректно уничтожить экземпляр Контроллера(((((
В методе, который запускается в потоке, есть булевский флаг, который контролирует цикл
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void CController::Execute()
{
    executeMutex.lock(); // Это попытка как-то исправить ситуацию...
    while (!m_bTerminate)
    {
            m_pView->StartDrawing(); //Сигнал "Визуализатору", что начинается фаза отображения объектов.
//До вызова FinishDrawing  внутри следующих процедур будут вызваться из "Визуализатора" процедуры отрисовки
 
        ManageFlyCycle(); //Здесь запускаются параллельные расчеты в вспомогательных потоках.
                    if (GameMode.eMode == emChase)
        {
            ReLinkInsect();//Определение новых пар для "погони".
            RunWaspCycle();
        }
        else
            TestFlyInfection();
 
        m_pView->FinishDrawing();//Сигнал "Визуализатору", что фаза отображения объектов завершена.
    }
    m_pView->OnControllerTerminate(); //Сигнал "Визуализатору" об уничтожении Контроллера
    executeMutex.unlock(); // Это попытка как-то исправить ситуацию...
}
В класса Контроллер есть метод
C++
1
2
3
4
5
void CController::Terminate()
{
    m_bTerminate = true;
    std::thread([=]() {executeMutex.lock(); executeMutex.unlock();}).detach();
}
который вызывается из "Визуализатора".
Манипуляции с executeMutex - это попытки как-то разобраться/понять, почему изменение флага m_bTerminate не приводит к завершению цикла (не вызывается m_pView->OnControllerTerminate()

Сам проект писан на C++ +wxWidgets. Могу выложить, если нужно.
0
Лучшие ответы (1)
Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
30.09.2023, 14:04
Ответы с готовыми решениями:

Прошу подсказки
Необходимо сделать интернет страницу не используя при этом фреймы так, чтобы нижнее поле (картинка...

Прошу подсказки
Ребят помогите. 2 ноутбука, один из них подключен к интернету. Можно ли сделать так чтобы второй...

прошу подсказки
Доброго времени суток, уважаемые. Прошу подсказать малоопытному. Работаы в IAR AVR 6.20 Задачка -...

Прошу подсказки
Вот простой код C++: int main() { int ppc= {17,7,18,56,3,64,71,88,91,15,11,115,130,11,20};...

50
фрилансер
5221 / 4757 / 1000
Регистрация: 11.10.2019
Сообщений: 12,475
30.09.2023, 14:22 2
Цитата Сообщение от Constcat Посмотреть сообщение
executeMutex.lock(); // Это попытка как-то исправить ситуацию...
    while (!m_bTerminate)
Цитата Сообщение от Constcat Посмотреть сообщение
executeMutex.unlock(); // Это попытка как-то исправить ситуацию...
это - попытка (удачная) сделать дедлок

m_bTerminate - какой тип этой переменной ? Надеюсь, это std::atomic<bool>

Добавлено через 1 минуту
Цитата Сообщение от Constcat Посмотреть сообщение
std::thread([=]() {executeMutex.lock(); executeMutex.unlock();}).
а тут этот дедлок и совершается. Поэтому и join залипает
А вообще, эта строка ничего не делает (кроме дедлока, хм)
0
223 / 191 / 34
Регистрация: 19.02.2021
Сообщений: 1,387
30.09.2023, 15:18  [ТС] 3
Цитата Сообщение от Алексей1153 Посмотреть сообщение
это - попытка (удачная) сделать дедлок
Цитата Сообщение от Алексей1153 Посмотреть сообщение
а тут этот дедлок и совершается. Поэтому и join залипает
Это акт отчаянья! Я уже неделю лажу по инету в поисках теории, но ничего внятного найти не могу.
И это не для join(), это для detach().
После того, как у меня зависал join() (без этих дурацких executeMutex.lock(); executeMutex.unlock()), я решил поэкспериментировать с detach().
Я думал вот как:
1. Перед рабочим циклом потока сделать executeMutex.lock().
2. Вызвав Terminate(), установить флаг и выполнить executeMutex.lock() (в другом методе, который в основном потоке. Может, в этом дело). Здесь (по моему ожиданию) все останавлиается.
3. Далее, Execute() отработав внутри цикла все процедуры, выходит по m_bTerminate и перед выходом из процедуры разблокирует мьютекс.
4. Что позволит продолжить код, стоящий на паузе в п.2.
5. А потом уже я могу нормально удалить Контроллер.

Добавлено через 1 минуту
Цитата Сообщение от Алексей1153 Посмотреть сообщение
Надеюсь, это std::atomic<bool>
Да. Не помогло.
Я его пытался делать и глобальным, и членом класса.
Походу, я что-то где-то упустил в теории.
Не могу понять, почему рабочий цикл не завершается по m_bTerminate...
0
фрилансер
5221 / 4757 / 1000
Регистрация: 11.10.2019
Сообщений: 12,475
30.09.2023, 15:31 4
Цитата Сообщение от Constcat Посмотреть сообщение
Перед рабочим циклом потока сделать executeMutex.lock().
мутекс должен локаться на очень краткое время - в критической секции, где нужен синхронизированных доступ к данным, использующимся в разных потоках. То есть, сейчас он у тебя используется как-то неправильно

Вообще, не видя проект, трудно что-то подсказать, поэтому прицепи какой-то пример кода, демонстрирующий проблему (если проект большой)

в самом проекте лично я копаться не буду, там, судя по всему, проще всё заново переписать

А кто-нибудь другой, вдруг, захочет покопаться
0
223 / 191 / 34
Регистрация: 19.02.2021
Сообщений: 1,387
30.09.2023, 16:43  [ТС] 5
Цитата Сообщение от Алексей1153 Посмотреть сообщение
Вообще, не видя проект, трудно что-то подсказать, поэтому прицепи какой-то пример кода, демонстрирующий проблему (если проект большой)
Там много чего нужно цеплять.)))
1. Контроллер является членом класса Визуализатора.
2. Визуализатор вызвает метод Контроллера, где устанавливается флаг. Вызывает в своем (основном) потоке.
3. А в этот момент Контроллер в дополнительном потоке может пинать Визуализатор для отрисовки объектов.
Тут такое...
На Delphi оно работает, как часики, но там собственный враппер над std::thread, возможно, там все гораздо грамотнее организовано, чем я наваял "с нуля")))

Может ли дело быть в том, что из метода, который запускается в основном потоке, устанавливается флаг для метода, который запускается в дополнительном потоке?

Добавлено через 41 минуту
Что показал эксперимент:
1. Отключил все дополнительные потоки, оставил только тот, где работает Контроллер.
2. Вывел m_bTerminate в статистику, которую постоянно отображает Визуализатор.
Вот ключевой код:
Хедер:
C++
1
2
3
4
5
6
7
class CController: public IControllerInterface, public IThreadInterface
{
//.................................................
private:
    std::atomic<bool> m_bTerminate;
//.................................................
}
Релизация:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void CController::Terminate()
{
    m_bTerminate = true;
}
 
void CController::Execute()
{
    while (!m_bTerminate)
    {
//Тут внутренние процедуры, они остались, работают в этом же потоке, что и Execute()
//Все вызовы Визуализатора закомментарены.
    }
    m_pView->OnControllerTerminate(); //Сюда мы должны попасть после изменения флага m_bTerminate
}
Создание и запуск Контроллера:
C++
1
2
3
4
5
6
void WaspFlyGLFrm::StartSimulation()
{
m_pController = new CController(this);//this - это Визулизатор.
m_pControllerThread = new std::thread(CController::Execute, m_pController);
//m_pController и m_pControllerThread - члены класса WaspFlyGLFrm
}
Остановка Контроллера:
C++
1
2
3
4
5
6
7
void WaspFlyGLFrm::DestroyController()
{
   if (m_pController && m_pControllerThread)
   {
        m_pController->Terminate(); //Terminate() описан выше, там только присвоение флага.
   }
}
Сюда мы должны прийти, если завершился рабочий цикл Контроллера:
void WaspFlyGLFrm::OnControllerTerminate()
C++
1
2
3
4
5
6
7
8
{
if (m_pControllerThread->joinable())
  m_pControllerThread->join();
delete m_pController;
delete m_pControllerThread;
m_pController = NULL;
m_pControllerThread = NULL;
}
Самое интересное, что мы сюда попадаем!
Дальше все тупо висит на строке m_pControllerThread->join() при этом Контроллер продолжает работать (это видно по отображаемой Визуализатором статистике), в которой видно, что m_bTerminate не изменился, так и остался false.

Как такое может быть?
С одной стороны - флаг изменился, т.к., из цикла мы вышли и попали в OnControllerTerminate()
С другой сторны - Контроллер продолжает работать. И показывает, что его флаг все еще false.

У меня праноидальное подозрение, что Контроллер каким-то образом то ли работает в нескольких потоках, то ли я вообще ничего не понимаю...

Добавлено через 8 минут
Цитата Сообщение от Constcat Посмотреть сообщение
C++
1
m_pControllerThread = new std::thread(CController::Execute, m_pController);
Здесь ошибка, должно быть
C++
1
m_pControllerThread = new std::thread(&CController::Execute, m_pController);
Исправил, но ничего не поменялось.
Висим на join(), поток продолжает работать, показывает, что флаг = false, хотя мы находимся в том месте, куда можно попасть только если флаг = true и процедура потока завершена.
0
223 / 191 / 34
Регистрация: 19.02.2021
Сообщений: 1,387
30.09.2023, 17:31  [ТС] 6
Вот объясните мне это:
Объявил m_bTerminate, как static:
Уперся в тупик с потоками. Прошу подсказки


Из Визуализатора директивно присвоил ему true:
Уперся в тупик с потоками. Прошу подсказки


Потом захожу в Terminate(), где должен установится этот же флаг в true и вижу там:
Уперся в тупик с потоками. Прошу подсказки


Это как??? Это одна переменная. Одна-единственная, глобальная, 100% проверил. Чего я не знаю?
0
223 / 191 / 34
Регистрация: 19.02.2021
Сообщений: 1,387
30.09.2023, 17:36  [ТС] 7
В случае
C++
1
static std::atomic<bool> m_bTerminate
то же самое.
В DestroyController() флаг получает значение true и тут же в Terminate() он уже false
При создании потока создается полная копия всех переменных, в том числе и статических? Или что это такое?
0
671 / 474 / 215
Регистрация: 06.09.2013
Сообщений: 1,301
30.09.2023, 17:38 8
Constcat, в OnControllerTerminate поток пытается вызвать join самого себя - это дедлок. Эта функция должна вызываться из другого потока (например, который запустил Execute).
0
223 / 191 / 34
Регистрация: 19.02.2021
Сообщений: 1,387
30.09.2023, 17:45  [ТС] 9
Цитата Сообщение от woldemas Посмотреть сообщение
в OnControllerTerminate поток пытается вызвать join самого себя - это дедлок. Эта функция должна вызываться из другого потока (например, который запустил Execute).
Это эксперимент был, я просто пробовал различные варианты.
План был такой:
1. В одной процедуре, которая крутится в фоновом потоке, захватывается мьютекс. Потом начинается какая-то длительная работа.
2. В другой процедуре из основного потока нужно подождать окончание этой работы (про сигналы и события я знаю, повторю - это эксперимент)))
3. Для этого в другой процедуре я пытаюсь захватить этот же мьютекс, процедура останавливается в этом месте и ждет.
4. Когда в первой процедуре работа закончена, там освобождается мьютекс.
5. После этого другая процедура захватывает этот мютекс, как признак, что первая процедура закончила работу.
0
фрилансер
5221 / 4757 / 1000
Регистрация: 11.10.2019
Сообщений: 12,475
30.09.2023, 17:59 10
Цитата Сообщение от Constcat Посмотреть сообщение
m_pControllerThread = new std::thread
тут не нужна динамика

C++
1
2
3
4
5
6
7
8
9
10
11
class ...........
{
....
    std::thread m_ControllerThread;
....
};
 
 
....
m_ControllerThread = std::thread(....
....
Добавлено через 1 минуту
Цитата Сообщение от Constcat Посмотреть сообщение
В одной процедуре, которая крутится в фоновом потоке, захватывается мьютекс. Потом начинается какая-то длительная работа.
Цитата Сообщение от Constcat Посмотреть сообщение
После этого другая процедура захватывает этот мютекс, как признак, что первая процедура закончила работу.
мутекс придумали не для этого. А для написанного выше достаточно флаг
0
671 / 474 / 215
Регистрация: 06.09.2013
Сообщений: 1,301
30.09.2023, 18:00 11
Цитата Сообщение от Constcat Посмотреть сообщение
Это эксперимент был, я просто пробовал различные варианты.
Мьютекс тут вообще не при чем, я про это:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void CController::Execute()
{
    while (!m_bTerminate)
    {
    }
    m_pView->OnControllerTerminate();
}
// ...
void WaspFlyGLFrm::OnControllerTerminate()
{
if (m_pControllerThread->joinable())
   m_pControllerThread->join(); // Тут будет висеть 
// ...
}
Ну если я правильно понимаю и m_pView экземпляр WaspFlyGLFrm
0
фрилансер
5221 / 4757 / 1000
Регистрация: 11.10.2019
Сообщений: 12,475
30.09.2023, 18:13 12
Лучший ответ Сообщение было отмечено Constcat как решение

Решение

Constcat, https://onlinegdb.com/XAPZqYDcb
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include <iostream>
#include <atomic>
#include <thread>
#include <chrono>
#include <memory>
 
class CController
{
private:
    std::atomic<bool> m_bTerminate{};
    std::thread m_ControllerThread; 
public:
    void Terminate()
    {
        if(m_ControllerThread.joinable())
        {
            m_bTerminate = true;
            m_ControllerThread.join();
        }
        m_bTerminate = false;
    }
     
    void Execute()
    {
        Terminate();
        m_ControllerThread=std::thread([&m_bTerminate=m_bTerminate]
        {
            using namespace std::chrono_literals;
            while(!m_bTerminate)
            {
                std::this_thread::sleep_for(100ms);
            }
        });
    }
    
    ~CController()
    {
        Terminate();
    }
};
 
class WaspFlyGLFrm
{
    std::unique_ptr<CController> m_Controller;
public:
    void StartSimulation()
    {
        m_Controller = std::make_unique<CController>();
        m_Controller->Execute();
    }
    
    void DestroyController()
    {
        m_Controller.reset();
    }
};
 
 
int main()
{
    using namespace std::chrono_literals;
    
    WaspFlyGLFrm w;
    w.StartSimulation();
 
    std::this_thread::sleep_for(3s);
    
    w.DestroyController();
}
1
223 / 191 / 34
Регистрация: 19.02.2021
Сообщений: 1,387
30.09.2023, 22:26  [ТС] 13
Алексей1153, Спасибо, возьму за образец, попробую.)))))
По результату отпишусь.
Получается, нужно не снаружи метод Контроллера запускать в потоке, а изнутри Контроллера...
Вот за эту подсказку - огромное спасибо!

Добавлено через 25 минут
Если не сложно, объясните, плз, эту конструкцию:
C++
1
2
3
4
m_ControllerThread=std::thread([&m_bTerminate=m_bTerminate]
{
...
}
Я имею ввиду, это присвоение в списке захвата.
0
2682 / 2151 / 674
Регистрация: 29.06.2020
Сообщений: 7,960
30.09.2023, 22:37 14
Цитата Сообщение от Constcat Посмотреть сообщение
Я имею ввиду, это присвоение в списке захвата.
именованный захват по ссылке

Добавлено через 4 минуты
Хотя более точно : захват по ссылке с инициализацией.
0
фрилансер
5221 / 4757 / 1000
Регистрация: 11.10.2019
Сообщений: 12,475
30.09.2023, 22:51 15
Цитата Сообщение от Constcat Посмотреть сообщение
Если не сложно, объясните, плз, эту конструкцию:
захват по ссылке с переименованием
(хотя, я дал полю лямбды такое же имя, как захваченная переменная)

но можно действительно сменить имя для ссылки, например

C++
1
m_ControllerThread=std::thread([&Terminate=m_bTerminate]
0
223 / 191 / 34
Регистрация: 19.02.2021
Сообщений: 1,387
30.09.2023, 23:20  [ТС] 16
Цитата Сообщение от Алексей1153 Посмотреть сообщение
захват по ссылке с переименованием
Ага, понял. Еще почитал про это в инете.
Но пришлось от лямбды отказаться. У меня там обращение к членам и методам класса, попытка передать в лямбду this почему-то не получилась (хотя я уже примерно понимаю, почему, там был логический косяк))))
Я завел еще один метод (в котором рабочий цикл) и его уже запускаю в потоке из Execute())

Все заработало! Во всяком случае, больше не виснет и не падает с SIGSEGV.

Сейчас переделаю пул вычислительных дополнительных потоков по вашему образцу, посмотрю, как оно будет работать.)))

В любом случае - огромное спасибо!
0
фрилансер
5221 / 4757 / 1000
Регистрация: 11.10.2019
Сообщений: 12,475
30.09.2023, 23:31 17
Цитата Сообщение от Constcat Посмотреть сообщение
попытка передать в лямбду this почему-то не получилась
this преспокойно захватывается по значению
0
0 / 0 / 0
Регистрация: 01.10.2023
Сообщений: 3
01.10.2023, 01:09 18
Цитата Сообщение от Алексей1153 Посмотреть сообщение
m_ControllerThread=std::thread([&Terminate=m_bTerminate]
Цитата Сообщение от Алексей1153 Посмотреть сообщение
захват по ссылке с переименованием
Не по теме: подскажите, почему именно такой захват, а не просто [this] например? Искал инфу, нашел что в с++11 перемещение в лямбду делали [var=std::move(var)] а в с++14 вот так.
0
фрилансер
5221 / 4757 / 1000
Регистрация: 11.10.2019
Сообщений: 12,475
01.10.2023, 06:40 19
4blK4blPblK, так я не использую this, использую всего одно поле класса.

Перемещать мне там не нужно, мне нужна именно ссылка
0
0 / 0 / 0
Регистрация: 01.10.2023
Сообщений: 3
01.10.2023, 11:52 20
Цитата Сообщение от Алексей1153 Посмотреть сообщение
использую всего одно поле класса.
а, то есть тут можно было и весь this захватить, но одно поле - экономнее? понял, спс.
0
01.10.2023, 11:52
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
01.10.2023, 11:52
Помогаю со студенческими работами здесь

Прошу подсказки и консультации
Поскажите пожалуйста какие таблицы нужно создать для создания базы данных к следующей задаче. Есть...

Прошу подсказки новичку
Господа оптимизаторы! Подскажите пожалуйста как правильно раскрутить сайт. Особенно интересует:...

Прошу подсказки по MVC и соединению с БД
В учебных целях пишу MVC приложение. Уперся в нормальную организацию запросов к базе данных через...

Прошу подсказки - определиться с идеологией
С++ builder 10. Подскажите пожалуйста как обычно делают? Собственно задача: Есть данные в таблице...

Прошу подсказки - куда копать?
При подключении второго монитора к VGA-выходу ноутбука Asus X301A ...

Прошу подсказки в настройке XenForo
Доброго всем времени суток! Поставил XenForo и возникла такая проблема: при попытке вставить в...

Не срабатывает clearInterval. Прошу подсказки
Доброго всем времени суток. Решил написать простенький скрипт эффекта печатания текста. var text...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
20
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2023, CyberForum.ru