2016-03-27 5 views
9

Ich verstehe nicht, warum eine andere Ebene der Indirektion erforderlich ist, wenn die GVL in Ruby C API freigegeben oder erworben wird.
Sowohl rb_thread_call_without_gvl() als auch rb_thread_call_with_gvl() erfordern eine Funktion, die nur ein Argument akzeptiert, was nicht immer der Fall ist. Ich möchte meine Argumente in einer Struktur nicht nur für den Zweck der Freigabe der GVL umschließen. Es erschwert die Lesbarkeit des Codes und erfordert das Umwandeln und das Leeren von Zeigern.
Nachdem ich in Ruby Threading-Code untersucht habe, fand ich die GVL_UNLOCK_BEGIN/GVL_UNLOCK_END Makros, die Pythons Py_BEGIN_ALLOW_THREADS/Py_END_ALLOW_THREADS entspricht, aber ich kann keine Dokumentation über sie finden und wenn sie sicher zu verwenden sind.
Es gibt auch die Makro wird innerhalb rb_thread_call_without_gvl() verwendet, aber ich bin mir nicht sicher, ob es sicher ist, es als eigenständiges Gerät ohne Aufruf rb_thread_call_without_gvl() selbst zu verwenden.Freigeben der globalen VM-Sperre in einer C-Erweiterung, ohne eine andere Funktion zu verwenden

Was ist der korrekte Weg zu sicher Freigabe der GVL in der Mitte des Ausführungsablaufs, ohne eine andere Funktion aufrufen zu müssen?

+0

Die API ist so konzipiert, dass Ihr C-Code explizit seine Absicht erklären muss, entweder mit oder ohne GVL zu laufen, so dass er im letzteren Fall die GVL vor der Rückkehr zu Ruby wiederfinden kann. Dies erfordert eine Funktion, die das Freigeben der GVL, das Ausführen des Codes und das Wiedererlangen der GVL einbezieht. Sie müssen Ihren Code in eine Funktion packen, um der Startfunktion mitzuteilen, was zu tun ist. Daher muss die API eine oder eine kleine Anzahl unterstützter Signaturen für die Benutzerfunktion auswählen, da jede Signatur eine separate Startfunktion benötigt. –

Antwort

7

In Ruby 2.x gibt es nur die rb_thread_call_without_gvl API. GVL_UNLOCK_BEGIN und GVL_UNLOCK_END sind Implementierungsdetails, die nur in thread.c definiert sind und daher für Ruby-Erweiterungen nicht verfügbar sind. So die direkte Antwort auf Ihre Frage ist "es gibt keine Möglichkeit, korrekt und die GVL freizugeben, ohne eine andere Funktion aufzurufen".

Es war früher eine "Region-based" API, rb_thread_blocking_region_begin/rb_thread_blocking_region_end, aber diese API wurde in Ruby 1.9.3 und entfernt in Ruby 2.2 (siehe https://bugs.ruby-lang.org/projects/ruby-trunk/wiki/CAPI_obsolete_definitions für den CAPI deprecation Zeitplan) veraltet.

Daher sind Sie leider mit rb_thread_call_without_gvl stecken.


Das sagte, es gibt ein paar Dinge, die Sie tun könnten, um den Schmerz zu lindern. In Standard C ist die Konvertierung zwischen den meisten Zeigern und void * implizit, sodass Sie keinen Cast hinzufügen müssen. Außerdem kann die Verwendung der bezeichneten Initialisierungssyntax die Erstellung der Argumentstruktur vereinfachen.

So können Sie

struct my_func_args { 
    int arg1; 
    char *arg2; 
}; 

void *func_no_gvl(void *data) { 
    struct my_func_args *args = data; 
    /* do stuff with args->arg... */ 
    return NULL; 
} 

VALUE my_ruby_function(...) { 
    ... 
    struct my_func_args args = { 
     // designated initializer syntax (C99) for cleaner code 
     .arg1 = ..., 
     .arg2 = ..., 
    }; 

    // call without an unblock function 
    void *res = rb_thread_call_without_gvl(func_no_gvl, &args, NULL, NULL); 
    ... 
} 

schreiben Obwohl diese nicht Ihr ursprüngliches Problem zu lösen, es ist zumindest macht es erträglicher (hoffe ich).

5

Wie kann die GVL in der Mitte des Ablaufs sicher freigegeben werden, ohne dass eine andere Funktion aufgerufen werden muss?

Sie müssen die mitgelieferte API oder die von Ihnen verwendete Methode verwenden, die irgendwann bricht. Die API an die GVL ist definiert in thread.h

void *rb_thread_call_with_gvl(void *(*func)(void *), void *data1); 
void *rb_thread_call_without_gvl(void *(*func)(void *), void *data1, 
     rb_unblock_function_t *ubf, void *data2); 
void *rb_thread_call_without_gvl2(void *(*func)(void *), void *data1, 
      rb_unblock_function_t *ubf, void *data2); 

Was Sie in der Kopfzeile finden, ist eine Vereinbarung zwischen Ihnen der Verbraucher ihrer API und der Autor des API. Betrachten Sie es als einen Vertrag. Alles, was Sie in einer .c insbesondere statischen Methoden und MACROS finden, sind nicht für den Verbrauch außerhalb der Datei, es sei denn, es ist in der Kopfzeile gefunden. Das Schlüsselwort static verhindert, dass dies geschieht, es ist einer der Gründe, warum es existiert und es ist am wichtigsten in C zu verwenden. Die anderen erwähnten Artikel sind in thread.c. Sie können in thread.c herumstochern, aber irgendetwas davon zu verwenden ist ein Verstoß gegen den Vertrag der API, dh es ist nicht sicher und wird es nie sein.

Ich schlage nicht vor, Sie tun dies, aber die einzige Möglichkeit für Sie zu tun, was Sie wollen, ist Teile ihrer Implementierung in Ihren eigenen Code zu kopieren, und dies würde eine Code-Überprüfung nicht bestehen. Die Menge an Code, die Sie kopieren müssten, würde wahrscheinlich alles in den Schatten stellen, was Sie tun müssten, um ihre APIs sicher zu verwenden.

Verwandte Themen