web-dev-qa-db-fra.com

Démarrage retardé d'un thread en C ++ 11

J'entre dans les threads C++ 11 et j'ai rencontré un problème.

Je veux déclarer une variable de thread comme globale et la démarrer plus tard.

Cependant, tous les exemples que j'ai vus semblent démarrer le fil immédiatement, par exemple

thread t(doSomething);

Ce que je veux c'est

thread t;

et démarrer le fil plus tard.

Ce que j'ai essayé c'est

if(!isThreadRunning)
{
    thread t(readTable);
}

mais maintenant t est la portée du bloc. Je veux donc déclarer t puis démarrer le thread plus tard afin que t soit accessible aux autres fonctions.

Merci pour toute aide.

31
Smithy

Le constructeur par défaut de std::thread Instancie un std::thread Sans démarrer ni représenter de thread réel.

std::thread t;

L'opérateur d'affectation déplace l'état d'un objet de thread et définit l'objet de thread attribué à son état initialisé par défaut:

t = std::thread(/* new thread code goes here */);

Cela crée d'abord un objet de thread temporaire représentant un nouveau thread, transfère la nouvelle représentation de thread dans l'objet de thread existant qui a un état par défaut et définit l'état de l'objet de thread temporaire sur l'état par défaut qui ne représente aucun thread en cours d'exécution. Ensuite, l'objet thread temporaire est détruit, sans rien faire.

Voici un exemple:

#include <iostream>
#include <thread>

void thread_func(const int i) {
    std::cout << "hello from thread: " << i << std::endl;
}

int main() {
    std::thread t;
    std::cout << "t exists" << std::endl;

    t = std::thread{ thread_func, 7 };
    t.join();

    std::cout << "done!" << std::endl;
}
44
Sam Varshavchik

Je donnerais au thread une variable de condition et un booléen appelé startRunning (initialement défini sur false). En fait, vous démarrez le thread immédiatement après sa création, mais la première chose qu'il fait est de se suspendre (en utilisant la variable_condition) et de ne commencer à traiter sa tâche réelle que lorsque la variable_condition est signalée de l'extérieur (et le startRunning = indicateur défini sur true).

MODIFIER: CODE PSEUDO:

// in your worker thread
{
    lock_guard l( theMutex );

    while ( ! startRunning )
    {
        cond_var.wait( l );
    }
}

// now start processing task


// in your main thread (after creating the worker thread)
{
    lock_guard l( theMutex );
    startRunning = true;
    cond_var.signal_one();
}

EDIT # 2: Dans le code ci-dessus, les variables theMutex, startRunning et cond_var doivent être accessibles par les deux threads. Que vous y arriviez en les rendant globales ou en les encapsulant dans une instance de structure/classe, cela dépend de vous.

2
antred

Il n'y a pas de "standard" de création d'un thread "suspendu" qui je suppose est ce que vous vouliez faire avec la bibliothèque de threads C++. Parce qu'il n'est pas pris en charge sur toutes les plates-formes qui ont des threads, il n'est pas là dans l'API C++.

  1. Vous souhaiterez peut-être créer une classe avec toutes les données nécessaires, mais pas réellement exécuter votre fonction de thread. Ce n'est pas la même chose que la création du fil de discussion mais peut être ce que vous voulez. Si c'est le cas, créez cela, puis liez plus tard l'objet et sa fonction operator() ou start() ou autre chose au thread.

  2. Vous voudrez peut-être l'ID de thread pour votre thread. Cela signifie que vous devez réellement démarrer la fonction de thread. Cependant, il peut commencer par attendre une variable de condition. Vous signalez ou diffusez ensuite cette variable de condition ultérieurement lorsque vous souhaitez qu'elle continue à s'exécuter. Bien sûr, vous pouvez demander à la fonction de vérifier une condition après sa reprise au cas où vous auriez décidé de la fermer et de ne pas l'exécuter après tout (auquel cas elle reviendra instantanément).

  3. Vous voudrez peut-être un std::thread objet sans fonction. Vous pouvez le faire et l'attacher à une fonction ultérieurement à run cette fonction dans un nouveau thread.

1
CashCow

Vous pouvez utiliser motif singleton . Ou je dirais plutôt antipattern .

Dans un singleton, vous auriez std::thread objet encapsulé. Lors du premier accès à singleton, votre thread sera créé et démarré.

0
GreenScape

déclaré pour la première fois dans la classe m_grabber ne fait rien. Nous attribuons un objet de classe membre avec un nouveau avec la fonction lambda dans launch_grabber La méthode et le thread avec lambda s'exécutent dans le contexte de classe source.

class  source {
...
 std::thread m_grabber;
 bool m_active;
...
}


bool source::launch_grabber() {
    // start grabber
    m_grabber = std::thread{ 
        [&] () {
            m_active = true;
            while (true)
            {
                if(!m_active)
                    break;
                // TODO: something in new thread

            }
        }
    };

    m_grabber.detach();
    return true;
}
0
Igor

Comme le dit antred dans sa réponse , vous pouvez utiliser une variable de condition pour faire attendre le thread au début de sa routine.

Scott Meyers dans son livre "Effective Modern C++" (dans le "Item 39: Consider void futures for one-shot event communication") propose d'utiliser void- future au lieu d'entités de niveau inférieur ( drapeau booléen, variable conditionnelle et mutex). Le problème peut donc être résolu comme ceci:

auto thread_starter = std::promise<void>;
auto thread = std::thread([starter_future = thread_starter.get_future()]() mutable {
    starter_future.wait(); //wait before starting actual work
    …; //do actual work
});
…; //you can do something, thread is like “paused” here
thread_starter.set_value(); //“start” the thread (break its initial waiting)

Scott Meyers met également en garde contre les exceptions dans le deuxième (Marqué par le commentaire you can do something, thread is like “paused” here). Si thread_starter.set_value() n'est jamais appelé pour certaines raisons (par exemple, en raison d'exceptions lancées dans le second ), Le thread attendra indéfiniment et toute tentative de le rejoindre entraînerait un blocage.

Comme les deux façons (basées sur condvar et basées sur l'avenir) contiennent une insécurité cachée et que la première manière (basée sur condvar) a besoin de code passe-partout, je propose d'écrire une classe wrapper autour de std::thread. Son interface doit être similaire à celle de std::thread (Sauf que ses instances doivent être assignables à partir d'autres instances de la même classe, pas à partir de std::thread), Mais contenir des void start() supplémentaires méthode.

Enveloppeur de fil basé sur l'avenir

class initially_suspended_thread {
    std::promise<bool> starter;
    std::thread impl;
public:
    template<class F, class ...Args>
    explicit initially_suspended_thread(F &&f, Args &&...args):
        starter(),
        impl([
            starter_future = starter.get_future(),
            routine = std::bind(std::forward<F>(f), std::forward<Args>(args)...)
        ]() mutable {if (starter_future.get()) routine();})
    {}
    void start() {starter.set_value(true);}
    ~initially_suspended_thread() {
        try {starter.set_value(false);}
        catch (const std::future_error &exc) {
            if (exc.code() != std::future_errc::promise_already_satisfied) throw;
            return; //already “started”, no need to do anything
        }
        impl.join(); //auto-join not-yet-“started” threads
    }
    …; //other methods, trivial
};

Enveloppeur de thread basé sur Condvar

class initially_suspended_thread {
    std::mutex state_mutex;
    enum {INITIAL, STARTED, ABORTED} state;
    std::condition_variable state_condvar;
    std::thread impl;
public:
    template<class F, class ...Args>
    explicit initially_suspended_thread(F &&f, Args &&...args):
        state_mutex(), state(INITIAL), state_condvar(),
        impl([
            &state_mutex = state_mutex, &state = state, &state_condvar = state_condvar,
            routine = std::bind(std::forward<F>(f), std::forward<Args>(args)...)
        ]() {
            {
                std::unique_lock state_mutex_lock(state_mutex);
                state_condvar.wait(
                    state_mutex_lock,
                    [&state]() {return state != INITIAL;}
                );
            }
            if (state == STARTED) routine();
        })
    {}
    void start() {
        {
            std::lock_guard state_mutex_lock(state_mutex);
            state = STARTED;
        }
        state_condvar.notify_one();
    }
    ~initially_suspended_thread() {
        {
            std::lock_guard state_mutex_lock(state_mutex);
            if (state == STARTED) return; //already “started”, no need to do anything
            state = ABORTED;
        }
        impl.join(); //auto-join not-yet-“started” threads
    }
    …; //other methods, trivial
};
0
Sasha