warten und benachrichtigen im gemeinsam genutzten C/C++-Speicher

Lesezeit: 5 Minuten

Benutzer-Avatar
Sajad Bahmani

Wie man wie in Java in C/C++ auf gemeinsam genutzten Speicher zwischen zwei oder mehr Threads wartet und benachrichtigt? Ich verwende die pthread-Bibliothek.

Benutzer-Avatar
Steve Jessop

Anstelle des Java-Objekts, das Sie zum Warten/Benachrichtigen verwenden würden, benötigen Sie zwei Objekte: einen Mutex und eine Bedingungsvariable. Diese werden mit initialisiert pthread_mutex_init und pthread_cond_init.

Wo Sie auf dem Java-Objekt synchronisiert hätten, verwenden Sie pthread_mutex_lock und pthread_mutex_unlock (Beachten Sie, dass Sie diese in C selbst manuell koppeln müssen). Wenn Sie nicht warten/benachrichtigen müssen, sondern nur sperren/entsperren, benötigen Sie die Bedingungsvariable nicht, nur den Mutex. Denken Sie daran, dass Mutexe nicht unbedingt “rekursiv” sind. Dies bedeutet, dass Sie, wenn Sie die Sperre bereits halten, sie nicht erneut übernehmen können, es sei denn, Sie setzen das Init-Flag, um zu sagen, dass Sie dieses Verhalten wünschen.

Wo du angerufen hättest java.lang.Object.waitAnruf pthread_cond_wait oder pthread_cond_timedwait.

Wo du angerufen hättest java.lang.Object.notifyAnruf pthread_cond_signal.

Wo du angerufen hättest java.lang.Object.notifyAllAnruf pthread_cond_broadcast.

Wie in Java sind falsche Wakeups von den Wait-Funktionen möglich, daher benötigen Sie eine Bedingung, die vor dem Call-to-Signal gesetzt und nach dem Call-to-Wait überprüft wird, und Sie müssen anrufen pthread_cond_wait in einer Schleife. Wie in Java wird der Mutex freigegeben, während Sie warten.

Im Gegensatz zu Java, wo Sie nicht anrufen können notify es sei denn, Sie halten den Monitor, Sie kann eigentlich anrufen pthread_cond_signal ohne den Mutex zu halten. Normalerweise bringt es dir aber nichts und ist oft eine wirklich schlechte Idee (denn normalerweise willst du sperren – Zustand setzen – signalisieren – entsperren). Es ist also am besten, es einfach zu ignorieren und wie Java zu behandeln.

Viel mehr steckt eigentlich nicht dahinter, das Grundmuster ist das gleiche wie bei Java, und das nicht zufällig. Lesen Sie jedoch die Dokumentation für all diese Funktionen, da es verschiedene Flags und lustige Verhaltensweisen gibt, über die Sie Bescheid wissen und/oder die Sie vermeiden möchten.

In C++ können Sie es etwas besser machen, als nur die pthreads-API zu verwenden. Sie sollten RAII zumindest auf die Mutex-Sperre/-Entsperrung anwenden, aber je nachdem, welche C++-Bibliotheken Sie verwenden können, ist es möglicherweise besser, einen eher C++-ähnlichen Wrapper für die pthreads-Funktionen zu verwenden.

Benutzer-Avatar
Domi

In Ihrem Titel verschmelzen Sie C und C++ so salopp zu “C/C++”. Ich hoffe, Sie schreiben kein Programm, das eine Mischung aus beidem ist.

Wenn Sie C++11 verwenden, finden Sie eine portable Alternative zu pthreads (auf POSIX-Systemen werden jedoch normalerweise pthreads unter der Haube verwendet).

Sie können verwenden std::condition_variable + std::mutex für warten/benachrichtigen. Dieses Beispiel zeigt wie:

#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <condition_variable>
 
std::mutex m;
std::condition_variable cv;
std::string data;
bool mainReady = false;
bool workerReader = false;
 
void worker_thread()
{
    // Wait until main() sends data
    {
        std::unique_lock<std::mutex> lk(m);
        cv.wait(lk, []{return mainReady;});
    }
 
    std::cout << "Worker thread is processing data: " << data << std::endl;
    data += " after processing";
 
    // Send data back to main()
    {
        std::lock_guard<std::mutex> lk(m);
        workerReady = true;
        std::cout << "Worker thread signals data processing completed\n";
    }
    cv.notify_one();
}
 
int main()
{
    std::thread worker(worker_thread);
 
    data = "Example data";
    // send data to the worker thread
    {
        std::lock_guard<std::mutex> lk(m);
        mainReady = true;
        std::cout << "main() signals data ready for processing\n";
    }
    cv.notify_one();
 
    // wait for the worker
    {
        std::unique_lock<std::mutex> lk(m);
        cv.wait(lk, []{return workerReady;});
    }
    std::cout << "Back in main(), data = " << data << '\n';
 

    // wait until worker dies finishes execution
    worker.join();
}

  • “Ich hoffe, Sie schreiben kein Programm, das eine Mischung aus beidem ist.” Was ist das Problem daran, beide zu mischen?

    – Michel Feinstein

    27. September 2019 um 1:54 Uhr

  • @mFeinstein In der Praxis mischt man sie ziemlich oft. Wenn Sie jedoch Zweifel haben und denken “Oh … soll ich einen rohen Zeiger oder einen intelligenten Zeiger verwenden?”, verwenden Sie bereits C++ (weil C keine intelligenten Zeiger hat), also möchten Sie auf jeden Fall intelligente Zeiger verwenden, es sei denn, es gibt einige API- oder andere Einschränkungen, die deren Verwendung verbieten oder eindeutig unnötig sind usw. Wenn Sie diese Entscheidung nicht automatisch treffen, lenken Sie sich ab und versuchen, zu viele unnötige Entscheidungen zu treffen, wodurch Sie Zeit und kognitive Ressourcen verschwenden kann für das Knacken schwierigerer Probleme ausgegeben werden.

    – Domi

    4. Oktober 2019 um 20:52 Uhr

pthread_cond_wait und pthread_cond_signal können verwendet werden, um basierend auf einer Bedingung zu synchronisieren

Verwenden Bedingungsvariablen ist eine Möglichkeit, dies zu tun: Diese sind verfügbar, wenn Sie die verwenden pthread Bibliothek unter Linux (siehe Link).

Eine Bedingungsvariable ist eine Variable vom Typ pthread_cond_t und wird mit den entsprechenden Funktionen zum Warten und späteren Fortsetzen des Prozesses verwendet.

Wenn Sie sich nicht um Portabilität kümmern, bietet Linux eventfd, das Ihnen genau das bietet, was Sie wollen. Jeder eventfd führt einen internen Zähler. Im Standardmodus wird das Lesen aus dem eventfd blockiert, wenn der Zähler Null ist, andernfalls kehrt es sofort zurück. Wenn Sie darauf schreiben, wird der interne Zähler hinzugefügt.

Der Warteruf wäre also nur ein uint64_t buf_a; read(event_fd, &buf_a, sizeof(buf_a));, wobei buf ein 8-Byte-Puffer sein muss. Um den wartenden Thread zu benachrichtigen, würden Sie tun uint64_t buf_b = 1; write(event_fd, &buf_b, sizeof(buf_b));.

Benutzer-Avatar
Brad Larson

Falls verfügbar, können Sie POSIX-Semaphore verwenden. Die pthread-Bibliothek enthält Mutexe, die möglicherweise auch für Sie funktionieren.

1385260cookie-checkwarten und benachrichtigen im gemeinsam genutzten C/C++-Speicher

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy