2017-02-21 5 views
3

Ich würde gerne eine Klasse schreiben, die std :: thread umschließt und sich wie ein std :: thread verhält, aber eigentlich nicht jedes Mal einen Thread zuweist, wenn ich etwas asynchron verarbeiten muss. Der Grund ist, dass ich Multithreading in einem Kontext verwenden muss, in dem ich nicht dynamisch zuweisen darf, und ich möchte auch nicht den Overhead haben, einen std :: thread zu erstellen.Einen Thread schreiben, der am Leben bleibt

Stattdessen möchte ich, dass ein Thread in einer Schleife ausgeführt wird, und warten, bis die Verarbeitung beginnen kann. Der Client ruft invoke auf, was den Thread aufweckt. Der Thread sperrt einen Mutex, verarbeitet ihn und schläft wieder ein. Eine Funktion join verhält sich wie std :: thread :: join durch Sperren, bis der Thread die Sperre freigibt (d. H. Wieder einschläft).

Ich denke, ich habe die Klasse zum Laufen bekommen, aber wegen der allgemeinen mangelnden Erfahrung im Multi-Threading möchte ich fragen, ob irgendjemand Race Conditions erkennen kann oder ob der von mir verwendete Ansatz als "guter Stil" angesehen wird. Zum Beispiel bin ich mir nicht sicher, ob das temporäre Sperren des Mutex eine vernünftige Möglichkeit ist, dem Thread beizutreten.

EDIT ich ein anderes Rennen Zustand gefunden: wenn join direkt nach invoke Aufruf, gibt es keinen Grund, der Thread bereits den Mutex gesperrt und verriegelt so den Anrufer von join bis der Faden schlafen geht. Um dies zu verhindern, musste ich eine Überprüfung für den Aufrufzähler hinzufügen.

Kopf

#pragma once 

#include <thread> 
#include <atomic> 
#include <mutex> 

class PersistentThread 
{ 
public: 
    PersistentThread(); 
    ~PersistentThread(); 

    // set function to invoke 
    // locks if thread is currently processing _func 
    void set(const std::function<void()> &f); 

    // wakes the thread up to process _func and fall asleep again 
    // locks if thread is currently processing _func 
    void invoke(); 

    // mimics std::thread::join 
    // locks until the thread is finished with it's loop 
    void join(); 

private: 

    // intern thread loop 
    void loop(bool *initialized); 

private: 

    bool       _shutdownRequested{ false }; 

    std::mutex      _mutex; 

    std::unique_ptr<std::thread> _thread; 
    std::condition_variable   _cond; 

    std::function<void()>   _func{ nullptr }; 
}; 

Quelldatei

#include "PersistentThread.h" 

    PersistentThread::PersistentThread() 
{ 
    auto lock = std::unique_lock<std::mutex>(_mutex); 
    bool initialized = false; 

    _thread = std::make_unique<std::thread>(&PersistentThread::loop, this, &initialized); 

    // wait until _thread notifies, check bool initialized to prevent spurious wakeups 
    _cond.wait(lock, [&] {return initialized; }); 
} 

PersistentThread::~PersistentThread() 
{ 
    { 
     std::lock_guard<std::mutex> lock(_mutex); 

     _func = nullptr; 
     _shutdownRequested = true; 

     // wake up and let join 
     _cond.notify_one(); 
    } 

    // join thread, 
    if (_thread->joinable()) 
    { 
     _thread->join(); 
    } 
} 

void PersistentThread::set(const std::function<void()>& f) 
{ 
    std::lock_guard<std::mutex> lock(_mutex); 
    this->_func = f; 
} 

void PersistentThread::invoke() 
{ 
    std::lock_guard<std::mutex> lock(_mutex); 
    _cond.notify_one(); 
} 

void PersistentThread::join() 
{ 
    bool joined = false; 
    while (!joined) 
    { 
     std::lock_guard<std::mutex> lock(_mutex); 
     joined = (_invokeCounter == 0); 
    } 
} 

    void PersistentThread::loop(bool *initialized) 
{ 

    std::unique_lock<std::mutex> lock(_mutex); 
    *initialized = true; 
    _cond.notify_one(); 

    while (true) 
    {  
     // wait until we get the mutex again 
     _cond.wait(lock, [this] {return _shutdownRequested || (this->_invokeCounter > 0); }); 

     // shut down if requested 
     if (_shutdownRequested) return; 

     // process 
     if (_func) _func(); 
     _invokeCounter--; 

    } 
} 
+5

Klingt so, als ob Sie einen [thread pool] möchten (https://en.wikipedia.org/wiki/Thread_pool) – NathanOliver

+0

Vielleicht sollte das auf Codereview SE gepostet werden? –

+0

@NathanOliver Ich zweite das. Einfach ein paar Threads auf einmal erstellen und sie dann verwenden. – Carcigenicate

Antwort

1

Sie fragen nach möglichen Rennbedingungen, und ich sehe mindestens eine Wettlaufbedingung in dem gezeigten Code.

Nach einem PersistentThread Konstruktion gibt es keine Garantie dafür, dass der neuen Thread wird seine Ausgangssperre in seinem loop() vor dem Haupt Ausführungs-Thread kehrt aus dem Konstruktor erwerben und tritt invoke(). Es ist möglich, dass der Hauptausführungsthread unmittelbar nach dem Abschluss des Konstruktors invoke() eingibt und niemand benachrichtigt, da der interne Ausführungsthread den Mutex noch nicht gesperrt hat. Als solches wird diese invoke() keine Verarbeitung zur Folge haben.

Sie müssen den Abschluss des Konstruktors mit der anfänglichen Sperre des Ausführungsthreads synchronisieren.

EDIT: Ihre Revision sieht richtig aus; aber ich habe auch eine andere Race Condition entdeckt.

Als documented in the description of wait(), wait() kann aufwecken "falsch". Nur weil wait() zurückgegeben wird, bedeutet das nicht, dass ein anderer Thread invoke() eingegeben hat.

Sie benötigen zusätzlich zu allem anderen einen Zähler, wobei invoke() den Zähler inkrementiert, und der Ausführungsthread seine zugewiesenen Aufgaben nur dann ausführt, wenn der Zähler größer als Null ist und dekrementiert. Dies schützt vor unerwünschten Weckern.

würde Ich habe auch den Ausführungs-Thread die Zähler überprüfen, bevor wait() und gebe wait() nur eintreten, wenn es 0 sonst, den Zähler dekrementiert, ihre Funktion ausführt, und springt zurück.

Dies sollte alle möglichen Rennen Bedingungen in diesem Bereich verstopfen.

P.S. Der fälschliche Weckruf gilt auch für die erste Benachrichtigung bei Ihrer Korrektur, dass der Ausführungsthread in die Schleife eingetreten ist. Sie müssen etwas ähnliches für diese Situation auch tun.

+0

Wäre es eine Lösung, eine 'auto lock = std :: unique_lock (_mutex);' hinzuzufügen, bevor Sie den Thread erstellen, fügen Sie '_cond.wait (lock);' nach der Erstellung hinzu und lassen Sie dann den Thread '_cond.notify_one aufrufen(); 'nachdem die lokale Sperre erstellt wurde? Ich kann auch die Quelle in der Frage bearbeiten, ... –

+0

Hinzugefügt, was ich nehme, ist eine Lösung in der Quelle –

+0

Ja, aber Ihr Job ist noch nicht fertig, bearbeitet ... –

0

Ich verstehe nicht, was Sie versuchen, genau zu fragen. Es ist ein schöner Stil, den du benutzt hast.

Es wäre viel sicherer mit bools und überprüfen Sie die einzelne routines, weil void nichts zurückgibt, so dass Sie möglicherweise durch Fehler stecken bleiben könnten. Überprüfen Sie alles, was Sie können, da der Faden unter der Kapuze verläuft. Stellen Sie sicher, dass die Anrufe korrekt ausgeführt werden, wenn der Prozess wirklich erfolgreich war. Außerdem könntest du einiges über "Thread Pooling" lesen.

+0

Sprechen Sie über die Verwendung von Boolen als Ergebnistyp für '_func'? Ich bin mir nicht sicher, wo dies problematische Szenarien verhindern würde. Ich habe das Gefühl, dass das Abfangen von Ausnahmen hep wäre, aber das ist es auch schon. –

+0

Catching Ausnahme wäre der gleiche Sinn am Ende, nur teurer – Lazcano

+0

@Lazcano Ausnahmen sind Null-Kosten, wenn sie nicht auftreten. Wenn sie das tun, sind sie jedoch viel teurer, aber das sollte nicht wirklich wichtig sein, da es in den meisten Fällen nicht sehr häufig vorkommt. – ProXicT

Verwandte Themen