2016-04-13 9 views
0

Ich habe versucht, ein Multi-Thread-Programm mit binären Semaphore zu implementieren. Hier ist der Code:Binäre Semaphore zur Aufrechterhaltung der Parallelität

#include <stdio.h> 
#include <stdlib.h> 
#include <pthread.h> 
#include <unistd.h> 
#include <semaphore.h> 

int g = 0; 

sem_t *semaphore; 

void *myThreadFun(void *vargp) 
{ 
    int myid = (int)vargp; 
    static int s = 0; 
    sem_wait(semaphore); 
    ++s; ++g; 
    printf("Thread ID: %d, Static: %d, Global: %d\n", myid, s, g); 
    fflush(stdout); 
    sem_post(semaphore); 
    pthread_exit(0); 
} 

int main() 
{ 

    int i; 
    pthread_t tid; 
    if ((semaphore = sem_open("/semaphore", O_CREAT, 0644, 3))==SEM_FAILED) { 
    printf("semaphore initialization failed\n"); 
    } 


    for (i = 0; i < 3; i++) { 
     pthread_create(&tid, NULL, myThreadFun, (void *)i); 
    } 

    pthread_exit(NULL); 
    return 0; 
} 

Nun, wenn ich die sempahore öffnete, machte ich die Zählung 3. Ich habe erwartet, dass dies würde nicht funktionieren, und ich würde Race-Bedingung erhalten, weil jeder Thread nun in der Lage ist, die von Erniedrigen Anzahl.

Ist etwas falsch mit der Implementierung? Wenn ich die Zählung während sem_open auf 0 setze, würde das auch keine Deadlock-Bedingung auslösen, da alle Threads auf sem_wait blockiert werden sollten.

+2

Was ist der Rückgabewert von 'sem_open (" Semaphor ", O_CREAT, 0644, 1);'? – EOF

+1

Haben Sie die Manpage für 'sem_open()' angeschaut, vor allem, was die Bedeutung des Rückgabewertes ist? – EOF

+0

Es ist nicht gleich SEM_FAILED – Semantics

Antwort

5

Nun, wenn ich die sempahore öffnete, machte ich die Zählung 3. Ich habe erwartet, dass dies würde nicht funktionieren, und ich würde Race-Bedingung erhalten, weil jeder Thread nun in der Lage ist, die Anzahl der Erniedrigen.

Und wie beurteilen Sie, dass es keine Rasse gibt? Das Beobachten der Leistung, die mit dem übereinstimmt, auf das Sie sich verlassen könnten, wenn kein Datenrennen stattfindet, beweist in keiner Weise, dass es kein Datenrennen gibt. Es liefert lediglich keinen Beweis dafür.

Allerdings scheinen Sie werden darauf hindeutet, dass es ein Daten Rennen inhärente in mehr als ein Faden gleichzeitig Durchführen ein sem_wait() auf einer Semaphore, dessen Wertes anfänglich größer als 1 (sonst welchen Zähler sprechen Sie?) . Aber das ist völliger Unsinn. Sie sprechen von einem Semaphor. Es ist ein Synchronisationsobjekt. Solche Objekte und die Funktionen, die sie manipulieren, sind die Grundlage für die Thread-Synchronisation. Sie selbst sind entweder völlig thread-safe oder terminal buggy.

Nun sind Sie richtig, dass Sie den Semaphor mit einer Anfangszählung öffnen, die ausreichend ist, um zu verhindern, dass Ihre Threads in blockieren, und dass sie daher alle gleichzeitig im gesamten Körper von myThreadFun() ausgeführt werden können. Sie haben jedoch nicht festgestellt, dass sie tatsächlich gleichzeitig ausführen. Es gibt mehrere Gründe, warum sie das nicht tun könnten. Wenn sie gleichzeitig ausgeführt werden, ist das Inkrementieren der gemeinsamen Variablen s und g in der Tat von Bedeutung, aber auch wenn Sie keine Anzeichen für ein Datenrennen sehen, bedeutet das nicht, dass es keins gibt.

Alles andere beiseite, die Tatsache, dass Ihre Fäden alle Anruf sem_wait(), sem_post() und printf() in Form von Speicherbarrieren eine Synchronisation induziert, was die Wahrscheinlichkeit des Beobachtens anomale Effekte auf s und g reduzieren würde. sem_wait() und sem_post() müssen Speicherbarrieren enthalten, um unabhängig von der aktuellen Anzahl der Semaphoren korrekt funktionieren zu können. printf() Aufrufe sind erforderlich, Sperren zu verwenden, um den Zustand des Streams vor Beschädigung in Multi-Thread-Programmen zu schützen, und es ist vernünftig anzunehmen, dass dies eine Speicherbarriere erfordert.

Ist etwas falsch mit der Implementierung?

Ja. Es ist nicht richtig synchronisiert. Initialisiere den Semaphor mit der Zahl 1, so dass die Modifikationen von s und g nur auftreten, wenn genau ein Thread den Semaphor gesperrt hat.

Auch, wenn ich die Zählung 0 während sem_open mache, würde das nicht eine Deadlock-Bedingung auslösen, weil alle Threads auf sem_wait blockiert werden sollten.

Wenn der Semaphor den Wert 0 hat, bevor einer der zusätzlichen Threads gestartet wird, dann ja. Es ist daher unangemessen, den Semaphor mit count 0 zu öffnen, es sei denn, du postest auch danach vor dem Start der Threads. Aber Sie verwenden eine namens Semaphor. Diese bleiben bestehen, bis sie entfernt werden, und Sie entfernen sie nie. Die von Ihnen angegebene Anzahl von sem_open() hat keine Auswirkungen, es sei denn, ein neuer Semaphor muss erstellt werden. Wenn ein existierender Semaphor geöffnet wird, ist seine Zählung unverändert.

Außerdem muss der Haupt-Thread alle anderen verbinden, bevor er beendet wird. Es ist nicht von Natur aus falsch, dies nicht zu tun, aber in den meisten Fällen ist es für die von Ihnen gewünschte Semantik erforderlich.

+0

Zuerst diese Aussage, "Sie scheinen darauf hinzuweisen, dass es ein Datenrennen in mehr als einem Thread in Verbindung mit einem sem_wait() in einem Semaphor geben würde, dessen Wert anfänglich größer als 1 ist (sonst von dem Sie sprechen ?). Aber das ist völliger Unsinn. " Sie nennen es völligen Unsinn und widersprechen es später, indem Sie sagen, dass die Veränderung globaler Variablen ein Problem ist. Das habe ich anfangs in der Aussage, die Sie Unsinn nannten, vermutet. – Semantics

+0

Zweitens macht die Zählung 0 während sem_open nicht den Deadlock, ich bekomme immer noch die Ausgabe. – Semantics

+1

Was Sie vorzuschlagen scheinen und was unsinnig ist, ist, dass gleichzeitige Aufrufe von 'sem_post()' ein Datenrennen beinhalten, "weil jeder Thread jetzt in der Lage ist, die Anzahl zu verringern". Keine Zählung wird irgendwo in Ihrem Code dekrementiert, außer für die Anzahl der verfügbaren Leases der Semaphore. –

0

Ich komme zu dem Code in einem Bit, das beweist, dass Sie eine Race-Bedingung hatten. Ich werde ein paar verschiedene Möglichkeiten hinzufügen, um es auszulösen, damit Sie sehen können, wie das funktioniert. Ich tue dies auf Linux und -std vorbei = gnu99 als param dh

gcc -Wall -pedantic -lpthread -std=gnu99 semaphore.c -o semtex 

Hinweis auf gcc. In Ihrem ursprünglichen Beispiel (unter der Annahme von Linux) war ein fataler Fehler, den Sie hatten, nicht, den Semaphor zu entfernen. Wenn Sie den folgenden Befehl ausführen, können Sie einige davon sitzen auf Ihrem Computer sehen

ls -la /dev/shm/sem.* 

Sie müssen sicherstellen, dass es keine alten Semaphore auf dem Dateisystem sitzt vor Ihrem Programm läuft oder Sie Kommissionierung bis am Ende der letzte Einstellungen aus dem alten Semaphor. Sie müssen sem_unlink verwenden, um aufzuräumen.

Um es zu verwenden, verwenden Sie bitte folgendes.

rm /dev/shm/sem.semtex;./semtex 

Ich mache bewusst sicher, dass die Semaphore vor dem Laufen, weil nicht da ist, wenn Sie ein Deadlock haben kann es sich um gelassen werden und die bewirkt, dass alle möglichen Probleme beim Testen.

Nun, wenn ich die sempahore öffnete, machte ich die Zählung 3. Ich erwartet hatte , dass dies nicht funktionieren würde, und ich würde Race-Bedingung erhalten, weil jeder Faden Dekrementieren der Zählung nun in der Lage ist.

Sie hatte eine Race-Bedingung, aber manchmal ist C so verdammt schnell Dinge können erscheinen zu arbeiten, weil Ihr Programm alles, was es in der Zeit, das Betriebssystem auf den Thread zugeordnet ist, dh das Betriebssystem nicht tat getan werden muss, bekommen preempt es an einem wichtigen Punkt.

Dies ist einer dieser Fälle, die Race-Bedingung ist da, Sie müssen nur ein wenig schielen, um es zu sehen. Im folgenden Code können Sie einige Parameter optimieren, um einen Deadlock, eine korrekte Verwendung und ein nicht definiertes Verhalten zu sehen.

#define INITIAL_SEMAPHORE_VALUE CORRECT 

Der Wert INITIAL_SEMAPHORE_VALUE kann drei Werte annehmen ...

#define DEADLOCK 0 
#define CORRECT 1 
#define INCORRECT 2 

Ich hoffe, sie sind selbsterklärend. Sie können auch zwei Methoden verwenden, um zu bewirken, dass die Wettlaufbedingung das Programm sprengt.

#define METHOD sleep 

Stellen Sie den METHOD zu spin und Sie können mit dem SPIN_COUNT und finden Sie heraus zu spielen, wie oft die Schleife ausgeführt werden können, bevor Sie tatsächlich sehen, ein Problem, das C ist, kann es noch viel zu tun, bevor es vorbelegt wird . Der Code enthält den größten Teil der Informationen, die Sie darin benötigen.

#include <assert.h> 
#include <errno.h> 
#include <fcntl.h> 
#include <pthread.h> 
#include <semaphore.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <sys/stat.h> 
#include <unistd.h> 
#define DEADLOCK 0 // DEADLOCK 
#define CORRECT 1 // CORRECT 
#define INCORRECT 2 // INCORRECT 
/* 
* Change the following values to observe what happen. 
*/ 
#define INITIAL_SEMAPHORE_VALUE CORRECT 
//The next value provides to two different ways to trigger the problem, one 
//using a tight loop and the other using a system call. 
#define METHOD sleep 

#if (METHOD == spin) 
/* You need to increase the SPIN_COUNT to a value that's big enough that the 
* kernel preempts the thread to see it fail. The value set here worked for me 
* in a VM but might not work for you, tweak it. */ 
#define SPIN_COUNT 1000000 
#else 
/* The reason we can use such a small time for USLEEP is because we're making 
* the kernel preempt the thread by using a system call.*/ 
#define USLEEP_TIME 1 
#endif 
#define TOT_THREADS 10 

static int g = 0; 
static int ret = 1729; 
sem_t *semaphore; 

void *myThreadFun(void *vargp) { 
    int myid = (int)vargp; 
    int w = 0; 
    static int s = 0; 
    if((w = sem_wait(semaphore)) != 0) { 
    fprintf(stderr, "Error: %s\n", strerror(errno)); 
    abort(); 
    }; 
/* This is the interesting part... Between updating `s` and `g` we add 
* a delay using one of two methods. */ 
    s++; 
#if (METHOD == spin) 
    int spin = 0; 
    while(spin < SPIN_COUNT) { 
    spin++; 
    } 
#else 
    usleep(USLEEP_TIME); 
#endif 
g++; 
    if(s != g) { 
    fprintf(stderr, "Fatal Error: s != g in thread: %d, s: %d, g: %d\n", myid, s, g); 
    abort(); 
    } 
    printf("Thread ID: %d, Static: %d, Global: %d\n", myid, s, g); 
    // It's a false sense of security if you think the assert will fail on a race 
    // condition when you get the params to sem_open wrong It might not be 
    // detected. 
    assert(s == g); 
    if((w = sem_post(semaphore)) != 0) { 
    fprintf(stderr, "Error: %s\n", strerror(errno)); 
    abort(); 
    }; 
    return &ret; 
} 

int main(void){ 
    int i; 
    void *status; 
    const char *semaphore_name = "semtex"; 
    pthread_t tids[TOT_THREADS]; 
    if((semaphore = sem_open(semaphore_name, O_CREAT, 0644, INITIAL_SEMAPHORE_VALUE)) == SEM_FAILED) { 
    fprintf(stderr, "Fatal Error: %s\n", strerror(errno)); 
    abort(); 
    } 
    for (i = 0; i < TOT_THREADS; i++) { 
    pthread_create(&tids[i], NULL, myThreadFun, (void *) (intptr_t) i); 
    } 
    for (i = 0; i < TOT_THREADS; i++) { 
    pthread_join(tids[i], &status); 
    assert(*(int*)status == 1729); 
    } 
    /*The following line was missing from your original code*/ 
    sem_unlink(semaphore_name); 
    pthread_exit(0); 
} 
+0

Was soll dieser Code tun? Ich meine, ich kann lesen und versuchen zu verstehen, aber was genau ist der Punkt. Außerdem haben Sie in sem_open die Anzahl nicht angegeben. Kompiliert es ohne Warnung? – Semantics

+0

@Semantics Ich habe den Code aktualisiert, um es ein wenig erklärender zu machen. Ich hoffe, das hilft alles aufzuräumen. Ping mich, wenn du mehr wissen musst. – Harry