Deutsch
SDK-Helfer/ Tools

Multithreading mit XProfan und Profan2Cpp

 

Jens-Arne
Reumschüssel
Hier stelle ich eine Methode vor, Multithreading mit XProfan zu realisieren.

-----
Hinweis: Das für diesen Artikel benötigte Zusatzprogramm Profan2Cpp gibt es hier:  [...] 
-----

Ja, XProfan ist nicht multithreadingfähig, das stimmt und bleibt bis auf weiteres auch so. Es gibt aber einen Weg, dies mit reinem XProfan als Programmiersprache doch zu realisieren. Dieser führt über den Umweg, dass die Prozedur, die als eigener asynchroner Thread gestartet werden soll, mit Profan2Cpp in eine C-DLL ausgelagert wird. Dies wird hier an einem einfachen Beispiel erläutert, welches einfach nur in drei XProfan-Text-Controls einen vorgegebenen Wert um 10.000 hochzählt. Jeder Counter läuft in einem eigenen Thread, der sich danach beendet und dabei über sendmessage einen Rückgabewert (hier das Handle seines Text-Controls) an eine im aufrufenden Programm definierte usermessage übermittelt.

Multithreading bietet den unschätzbaren Vorteil, im "Hintergrund" Aufgaben erledigen zu können, während das Hauptprogramm weiterläuft, und dabei Handles und Speicherelemente austauschen zu können. XProfan ist zwar multiprozessingfähig, aber dabei läuft der neue Prozess in einem getrennten Adressraum, was die Rückgabe von neu angelegten Handles etc. verhindert. Multithreading hingegen bietet genau dies: einen gemeinsamen Adressraum von aufrufendem Programm und dem neuen Thread. Aber Achtung, dies birgt auch Risiken. Hierzu bitte unbedingt die Hinweise ganz unten unter den Code-Beispielen lesen! Diese sind bestimmt noch nicht vollständig, Ergänzungen sind gerne willkommen.

Beide Code-Beispiele sind intensiv kommentiert, sodass ich hoffe, dass man ohne Weiteres versteht, was dort geschieht.

Hier zunächst die XProfan-Prozedur, die mit Profan2Cpp in eine C-DLL übersetzt werden muss (bitte die Datei MultithreadingBeispielC.prf nennen und die erzeugte DLL in das Verzeichnis des XProfan-Hauptprogramms kopieren, das danach als Code-Beispiel folgt):

[Alternativ gibt's die fertige DLL auch hier, falls jemand mit Profan2Cpp im Moment nicht up to date ist: Herunterladen]
 $DLL

DLLPROC Count_C,1,"Count_C"

    parameters param#
    declare i&,a&,s$,hAufrufer&,ThreadRunning#,hText&,StartWert&
    s$=@string$(param#,0)
    'als erstes das Flag für das aufrufende Programm setzen, dass der Thread läuft
    a&=val(@substr$(s$,2,"²~³"))
    ThreadRunning#=a&
    long ThreadRunning#,0=1
    'Aufrufer-Fensterhandle ermitteln
    hAufrufer&=@val(@substr$(s$,1,"²~³"))
    'Textcontrol-Handle ermitteln
    hText&=@val(@substr$(s$,3,"²~³"))
    'Startwert ermitteln
    StartWert&=@val(@substr$(s$,4,"²~³"))
    'Zählung durchführen
    i&=StartWert&

    while i&<=StartWert&+10000

        settext hText&,@str$(i&)
        inc i&

    endwhile

    'Rückgabewert übermitteln
    @sendmessage(hAufrufer&,$1000,hText&,0)
    long ThreadRunning#,0=0'Flag für das aufrufende Programm dafür, dass der Thread läuft, löschen
    return 0

ENDPROC


Nun folgt das normale XProfan-Programm, das die drei Threads, die eingangs erwähnt wurden, aufruft und auf deren Ende wartet:
'Multithreading-Beispiel mit XProfan-ThreadProc in C-DLL über Profan2Cpp
 $H windows.ph
declare hDLL%,uwp&,CProcAddr&
declare hText1&,hText2&,hText3&
declare s1$,s2$,s3$
declare hThread1&,hThread2&,hThread3&
declare ThreadRunning1&,ThreadRunning2&,ThreadRunning3&
cls ~getsyscolor(4)'Hauptfenster mit Hintergrundgrau erstellen (weil die Text-Dialogelemente in dieser Farbe gehalten sind)
usermessages $1000'mit dieser Message signalisiert ein Thread, dass er sich nun beendet (das kann zusätzlich mit den Flags ThreadRunningX& überprüft werden, was in diesem Beispiel aber nicht benutzt wird)
hText1&=@create("TEXT",%HWnd,"0",10,10,300,20)'hier wird Thread 1 reinschreiben
hText2&=@create("TEXT",%HWnd,"0",10,35,300,20)'hier wird Thread 2 reinschreiben
hText3&=@create("TEXT",%HWnd,"0",10,60,300,20)'hier wird Thread 3 reinschreiben
hDLL%=@usedll("MultithreadingBeispielC.dll")'in dieser DLL ist die Thread-Prozedur enthalten
'Threads starten
CProcAddr&=~GetProcAddress(hDLL%,"Count_C")'"echte" ProcAddr der in die DLL ausgelagerten Prozedur beziehen (mit direkten Profan-Prozeduren geht das nicht, sodass Profan selbst nicht multithreading-fähig ist)
s1$=@str$(%HWnd)+"²~³"+@str$(@addr(ThreadRunning1&))+"²~³"+@str$(hText1&)+"²~³1"'Übergabestring für Thread 1 mit dem Hauptfensterhandle, dem Flag ThreadRunning, dem Handle des Text-Dialogelements und dem Startwert füttern
hThread1&=~createthread(0,0,CProcAddr&,@addr(s1$),0,0)'Thread 1 starten
s2$=@str$(%HWnd)+"²~³"+@str$(@addr(ThreadRunning2&))+"²~³"+@str$(hText2&)+"²~³-10000"'Übergabestring für Thread 2 mit dem Hauptfensterhandle, dem Flag ThreadRunning, dem Handle des Text-Dialogelements und dem Startwert füttern
hThread2&=~createthread(0,0,CProcAddr&,@addr(s2$),0,0)'Thread 2 starten
s3$=@str$(%HWnd)+"²~³"+@str$(@addr(ThreadRunning3&))+"²~³"+@str$(hText3&)+"²~³12345678"'Übergabestring für Thread 3 mit dem Hauptfensterhandle, dem Flag ThreadRunning, dem Handle des Text-Dialogelements und dem Startwert füttern
hThread3&=~createthread(0,0,CProcAddr&,@addr(s3$),0,0)'Thread 3 starten
'Auf das Ende der Threads warten und reagieren

while (@gettext$(hText1&)<>"Fertig.") or (@gettext$(hText2&)<>"Fertig.") or (@gettext$(hText3&)<>"Fertig.")

    waitinput

    if %umessage=$1000'ein Thread signalisiert, dass er sich jetzt beendet und übergibt dabei sein Texthandle in &UwParam

        uwp&=&UwParam

        if uwp&=hText1&

            settext hText1&,"Fertig."

        elseif uwp&=hText2&

            settext hText2&,"Fertig."

        elseif uwp&=hText3&

            settext hText3&,"Fertig."

        endif

    endif

endwhile

waitinput'damit man das Ende mit 3x "Fertig." sieht
'aufräumen
~closehandle(hThread1&)
~closehandle(hThread2&)
~closehandle(hThread3&)
usermessages 0
freedll hDLL%
end

Hinweise:
· Ein auf diese Weise erzeugter Thread benutzt den Adressraum des aufrufenden Programms, weswegen problemlos Handles ausgetauscht werden können. Dabei läuft das Hauptprogramm weiter, während der Thread im "Hintergrund" (=asynchron) arbeitet. Ich benutze z.B. einen solchen Thread, um in einem Bildanzeigeprogramm das jeweils nächste Bild vorzuladen und dessen Handle an das aufrufende Programm zurückzuübermitteln (mittels der im Beispiel benutzten sendmessage-Methode).

· Einem neu zu startenden Thread kann nur exakt ein Parameter übergeben werden. Es bietet sich daher an, wie in dem Beispiel hierzu einen String zu benutzen, in dem alle notwendigen Informationen mit einem möglichst unzweideutigen Separator getrennt (hier "²~³") enthalten sind.

· Es gibt eine Windows-API-Funktion "GetExitCodeThread", die u.a. ermitteln soll, ob ein Thread noch aktiv ist. Diese Funktion funktioniert bis heute (Windows 10) nicht zuverlässig, sondern liefert dann und wann unvorhersehbar ein Ende des Threads zurück, obwohl dieser noch läuft. Daher ist in dem obigen Beispiel schon der Workaround eingebaut, der aber in dem Beispiel nicht benutzt wird: Der Thread sollte zu seinem Beginn ein Flag, dessen Adresse ihm übergeben wurde, auf 1 setzen, das dem aufrufenden Programm dessen Ausführung signalisiert, und direkt vor dem Thread-Ende sollte dieses Flag auf 0 gesetzt werden.

· whileloop sollte in einem Thread nicht benutzt werden. Dies könnte mit weiteren whileloops im aufrufenden Programm oder simultan laufenden anderen Threads kollidieren. while mit Zählervariable ist unter C ohnehin genauso schnell.

· Wenn man im Thread externe Funktionen aufruft, die auch im aufrufenden Programm (oder in weiteren Threads) gleichzeitig benutzt werden könnten, muss man diese Kollision mit einem gemeinsam genutzten Flag abfangen; es muss dann im jeweiligen Thread/im aufrufenden Programm so lange gewartet werden, bis die Funktion wieder frei ist. Sonst kann es zu einem Absturz ohne Fehlermeldung kommen.

Das Prinzip der gemeinsamen Nutzung von Flags sollte durch den Parameter-Übergabestring im Beispiel klar geworden sein. Immer daran denken: Es wird, anders als beim Multiprozessing, ein gemeinsamer Adressraum aller Threads und des aufrufenden Programmes verwendet, was gewünscht ist, aber auch zu Kollisionen führen kann.

· Im aufrufenden Programm bereits initialisierte DLLs (usedll) dürfen in der Thread-DLL nicht noch einmal initialisiert werden (sonst Programmabsturz). Die Einbindung der entsprechenden INC-Datei reicht.

· Alle mit ~createthread erstellten Threads müssen nach deren Beendigung mit ~closehandle wieder entfernt werden.

Viel Spaß beim Ausprobieren, und Anregungen sind natürlich jederzeit willkommen!

232 kB
Bezeichnung:BeispielC.dll
Version:1.0
Kurzbeschreibung: Fertige DLL für das Multithreading-Beispiel
Hochgeladen:20.06.2018
Ladeanzahl160
Herunterladen
 
XProfan X4 * Prf2Cpp * XPSE * JRPC3 * Win11 Pro 64bit * PC i7-7700K@4,2GHz, 32 GB RAM
PM: jreumsc@web.de
19.06.2018  
 




p.specht

Tolle Sache - Danke!
 
XProfan 11
Computer: Gerät, daß es in Mikrosekunden erlaubt, 50.000 Fehler zu machen, zB 'daß' statt 'das'...
20.06.2018  
 



Coole Idee!

Treffenderer Titel wäre vielleicht "Multithreading per Profan2Cpp", da Multithreading mit XProfan entweder ab X4 per Inline-ASM-Funktionsadressen oder per xpse mit nProcs ( Threadbeispiele  [...]   [...]  ) durchaus sonst relativ einfach anzuwenden ist.
cls
// 3 threads per myThread-Proc
createThread(,,procAddr(myThread,1),,,)
createThread(,,procAddr(myThread,1),,,)
createThread(,,procAddr(myThread,1),,,)
waitinput

nproc myThread

    parameters data&
    //...
    return data&

endproc

 
20.06.2018  
 



[offtopic]
Habe das Thema in die Bibliothek nach SDK-Helfer und Tools verschoben.
[/offtopic]
 
20.06.2018  
 




Jens-Arne
Reumschüssel
Moin,

ja klar, das geht natürlich schon lange mit nProcs, und das ist selbstverständlich absolut der Erwähnung wert. Allerdings ist man mit "normalen" XProfan-Procs flexibler. In meinem erwähnten Grafikanzeigeprogramm funktionieren nProcs leider nicht, weil XPSE da irgendwo aussteigt (nur, wenn man nProcs verwenden möchte, nicht generell). Wo, weiß ich nicht, weil ich noch keine Lust hatte, 16.000 Zeilen nach und nach auszukommentieren, um die Stelle zu finden, wo es hakt, aber das macht nichts. Grundsätzlich geht es ja. Das mit den Inline-ASM-Funktionsadressen ist mir übrigens nicht klar. Kann man damit normale Prozeduren zugänglich machen, oder müssen die in Assembler geschrieben sein?

Beste Grüße, Jens-Arne
 
XProfan X3
XProfan X4 * Prf2Cpp * XPSE * JRPC3 * Win11 Pro 64bit * PC i7-7700K@4,2GHz, 32 GB RAM
PM: jreumsc@web.de
23.06.2018  
 



Momentan müssten sie noch in Assembler geschrieben worden sein.

Deshalb würde mich ein XPSE-Update reizen, dass das "nProfan" in XProfan-Inline-Asm umsetzt. Ich könnte da vermutlich auch viel "Overhead" einsparen und neue Ordnung in XPSE bringen. Aber der Reihe nach...
 
26.06.2018  
 



Antworten


Thementitel, max. 100 Zeichen.
 

Systemprofile:

Kein Systemprofil angelegt. [anlegen]

XProfan:

 Beitrag  Schrift  Smilies  ▼ 

Bitte anmelden um einen Beitrag zu verfassen.
 

Themenoptionen

6.121 Betrachtungen

Unbenanntvor 0 min.
Sven Bader21.11.2023
funkheld25.09.2023
Thomas24.04.2023
Axel Berse20.02.2023
Mehr...

Themeninformationen



Admins  |  AGB  |  Anwendungen  |  Autoren  |  Chat  |  Datenschutz  |  Download  |  Eingangshalle  |  Hilfe  |  Händlerportal  |  Impressum  |  Mart  |  Schnittstellen  |  SDK  |  Services  |  Spiele  |  Suche  |  Support

Ein Projekt aller XProfaner, die es gibt!


Mein XProfan
Private Nachrichten
Eigenes Ablageforum
Themen-Merkliste
Eigene Beiträge
Eigene Themen
Zwischenablage
Abmelden
 Deutsch English Français Español Italia
Übersetzungen

Datenschutz


Wir verwenden Cookies nur als Session-Cookies wegen der technischen Notwendigkeit und bei uns gibt es keine Cookies von Drittanbietern.

Wenn du hier auf unsere Webseite klickst oder navigierst, stimmst du unserer Erfassung von Informationen in unseren Cookies auf XProfan.Net zu.

Weitere Informationen zu unseren Cookies und dazu, wie du die Kontrolle darüber behältst, findest du in unserer nachfolgenden Datenschutzerklärung.


einverstandenDatenschutzerklärung
Ich möchte keinen Cookie