Остановить выполнение метода класса в потоке

Я создал класс С++ с именем Timer, у него есть 3 метода:

  • Начало()
  • остановка()
  • Распечатать()

Метод start() включает флаг с именем run, присваивая ему значение true.

Метод stop(), отключите флаг, установив его значение в false.

Метод print() выполняет while() при условии, если run == true, и печатает некоторый текст, ожидающий полсекунды.


Таймер.hpp

#ifndef TIMER
#define TIMER 1

#include <iostream>
#include <cstdbool>

#include <unistd.h>

class Timer{
private:
    bool run;

public:
    void start();
    void stop();
    void print();

};

#endif

Таймер.cpp

#include "Timer.hpp"

void Timer::start(){
    this->run = true;
    this->print();
    return;
}

void Timer::stop(){
    this->run = false;
    return;
}

void Timer::print(){

    int counter = 0;

    while(this->run == true){

        std::cout << counter << std::endl;
        counter++;

        usleep(500000);
    }

    return;
}

main.cpp

#include <pthread.h>

#include "Timer.hpp"

void *handler(void *argument){

    ((Timer *) argument)->start();

    return argument;
}

int main(void){

    Timer *timer = new Timer();
    pthread_t timer_thread;
    int mainCounter = 0;

    pthread_create(&timer_thread, NULL, handler, (void *) &timer);

    while(true){

        if (mainCounter == 100){
            std::cout << "Stopping..." << std::endl;
            timer->stop();
        }

        std::cout << " => " << mainCounter << std::endl;
        mainCounter++;

        usleep(50000);
    }

    return 0;
}

Моя проблема.

Я создал поток для обработки выполнения метода start(), после того как я создал условие внутри основного потока, в котором после mainCounter выполнения 100 итераций он выполняет timer->stop(), но не останавливает цикл таймера.

Когда mainCounter достигает 100-й итерации, он не может остановить цикл внутри потока.

Инструкция по компиляции:

g++ Timer.cpp -c 
g++ Timer.cpp main.cpp -o main -lpthread

Результат:

9
 => 90
 => 91
 => 92
 => 93
 => 94
 => 95
 => 96
 => 97
 => 98
 => 99
10
Stopping...
 => 100
 => 101
 => 102
 => 103
 => 104
 => 105
 => 106
 => 107
 => 108
 => 109
11

Попробуйте онлайн!

Если вы собираетесь читать и записывать переменную (например, вашу логическую переменную run) из нескольких потоков, вам нужно либо защитить все обращения к этой переменной с помощью мьютекса, либо сделать переменную атомарной с помощью std::atomic. В противном случае вы вызываете неопределенное поведение, и ваша программа может не делать того, что вы от нее ожидаете. (см.: stackoverflow.com/questions/31978324/что именно-это-stdatom‌​ic )

Jeremy Friesner 09.04.2019 19:07

Знаете ли вы, что никогда не выходите из цикла в вашей основной теме?

r3mus n0x 09.04.2019 19:11

@ r3musn0x Я знаю, что никогда не выходил из основного потока, проблема в том, что я хочу остановить цикл внутри потока.

Ivan Botero 09.04.2019 19:15

@IvanBotero, из предоставленного вами вывода кажется, что основной поток продолжает печать, а вторичный поток, возможно, уже завершился.

r3mus n0x 09.04.2019 19:16

Опечатка-> (void *) &timer) таймер уже является указателем. Вы передаете указатель на указатель и используете его (((Timer *) argument)->start();) как простой указатель. Потерять &

user4581301 09.04.2019 19:19

Вы фактически остановили рабочий поток. Это основной поток, который продолжает работать, печатая эти значения.

Timmy_A 09.04.2019 19:21

Рабочий поток @Timmy_A, вероятно, не был остановлен. this внутри Timer::print не указывает на то же самое, что и timer

user4581301 09.04.2019 19:24

@ user4581301 Конечно. Пользователь отредактировал свой вывод, когда я писал свой ответ.

Timmy_A 09.04.2019 19:26

Попробуйте пометить поле участника запуска ключевым словом volatile. Но настоящую ошибку @user4581301 уже описал. Проблема заключается в использовании указателя на указатель вместо указателя. В рабочем потоке вы изменяете другую память, а не ту, которая принадлежит переменной «таймер». Таким образом, для «запуска» никогда не устанавливается значение «истина».

Timmy_A 09.04.2019 19:29

Спасибо за ваши комментарии, я добавил пример поверх ideone.com.

Ivan Botero 09.04.2019 19:34
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
10
161
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Как уже упоминал @user4581301, это решит проблему:

pthread_create(&timer_thread, NULL, handler, (void *) &timer);

должно быть

pthread_create(&timer_thread, NULL, handler, (void *) timer);

Проблема в том, что timer указывает на выделенный Timer класс, а &timer указывает где-то в стеке. Во время выполнения потока вы пытаетесь получить доступ к члену класса run, но поскольку this указывает на стек, вы на самом деле читаете неверное значение.

Другие примечания: объявите bool run как std::atomic_bool run или используйте любой механизм мьютекса для потокобезопасности. Кроме того, всегда удаляйте выделенные вами переменные: delete timer.

Лучшая формулировка: от указывает где-то в стеке до указывает на себя timer и уже является указателем., потому что мы точно знаем, куда он указывает. Есть немного более разумная альтернатива: во-первых, не используйте динамическое размещение: Timer timer; Тогда вы сможете (void *) &timer, timer.stop(); и не утечь timer, потому что это не было deleted. Что касается атомной проблемы, я хакер, поэтому я бы, вероятно, использовал volatile sig_atomic_t run;

user4581301 09.04.2019 19:54

Другие вопросы по теме