MultiThreading Abstraktion CPU



  • Hallo Leute,

    ich mache hier und da Software, auch mit parallelisiert mit Threads etc. und habe mir kaum Gedanken gemacht wie Software-Threads auf die CPU-Thread abstrahiert werden.

    Wenn ich eine einfach (Single, One Core CPU habe) und ein OS , werden meine Threads ja durch preemptive- Multitasking "parallelisiert", wenn ich kein OS Habe kann ich es selbst mit kooperative Multistasking machen ok...

    Aber ich denke es ist weitaus komplexer wenn ich Multi-Core Mulit- Threaed CPUs habe, da muss ja das OS spezifsche Threading ja irgendwie CPU abstrahieren.... wie funktioniert das , gibt da gute Artikel dazu?:) Wie sehen die API's (windows) aus wenn ich meine Aufgaben konkrete auf kerne verteilen möchte... weil das passiert ja sonst unter der Haube;)

    Danke euch schonmal;=)



  • @SoIntMan sagte in MultiThreading Abstraktion CPU:

    Wie sehen die API's (windows) aus wenn ich meine Aufgaben konkrete auf kerne verteilen möchte... weil das passiert ja sonst unter der Haube;)

    Gleich vorneweg, ich habe mit Parallelisierung noch nicht viel gemacht.

    Mein klassischer Ansatzpunkt wäre mittels OpenMP.

    Seit den neusten C++ Versionen (C++17) gibt es auch rudimentäre Unterstützung für paralelle Aufgaben (siehe std::execution::parallel_policy). Auch eine schöne Quelle: Using C++17 Parallel Algorithms for Better Performance



  • @SoIntMan
    Vorweg: vergessen wir mal Hyper-Threading & Co. Ob etwas zwei Cores hat oder einen Core der so tut als wäre er zwei Cores spielt erstmal keine Rolle. Genau so vergessen wir dass es mehrere CPUs statt einer CPU mit mehreren Cores geben könnte - auch das ist erstmal nicht wichtig.


    Wenn ich es richtig verstanden habe, dann läuft es mit multi-core CPUs im Prinzip genau so wie mit single-core -- nur halt dass dann mehrere Cores parallel preemptives Multithreading machen. D.h. jeder Core bekommt seinen eigenen Scheduler-Interrupt, und wechselt dann welchen Thread er ausführt. Natürlich darf er nicht einen Thread nehmen der schon auf einem anderen Core läuft - aber das ist ja einfach zu bewerkstelligen.

    z.B. kann man einen globalen Spin-Lock für die Thread-Liste machen. Wenn ein Core den Thread wechseln möchte schnappt er sich erstmal diesen Spin-Lock. Dann markiert er den alten Thread als "ready", sucht sich den Thread aus der als nächstes laufen soll, und markiert diesen dann als "running". Dann gibt er den Spin-Lock frei und schaltet auf den neuen Thread um.

    Dinge wie ein paar Cores schlafenlegen, wenn es zu wenig zu tun gibt, und sie wieder aufwecken wenn neue "ready" Threads dazukommen, sind vermutlich ein bisschen komplizierter. Aber ich denke da muss man nicht im Detail wissen wie das geht um das grundlegende Prinzip zu verstehen.

    Wie sehen die API's (windows) aus wenn ich meine Aufgaben konkrete auf kerne verteilen möchte... weil das passiert ja sonst unter der Haube;)

    Das passiert bei Threads immer unter der Haube. Du kannst nur ein paar Schräubchen drehen. Auf Windows kannst du z.B. die Thread-Priorität setzen. Das beeinflusst dann welche Threads den Grossteil der Rechenzeit bekommen wenn es mehr "ready" Threads gibt als Cores.

    Wobei man sich auf die Thread-Prioritäten nicht zu 100% verlassen kann. Der Windows Scheduler sorgt z.B. dafür dass zu jedem Zeitpunkt der Thread mit der höchsten Priorität läuft (bzw. einer der Threads mit der höchsten Priorität). Auf einem System mit 2+ Cores kannst du aber nicht davon ausgehen dass zu jedem Zeitpunkt auch der Thread mit der zweithöchsten Priorität läuft.

    Beispiel: du hast 2 Cores und 3 Threads: A mit Priorität 1, B mit 2, und C mit 3.
    Core 1 bearbeitet gerade den Thread C
    Core 2 bearbeitet gerade den Thread A
    Der Thread B schläft, sagen wir weil er auf eine Mutex wartet.

    Wenn jetzt der Thread C auf Core 1 die Mutex freigibt, wird der Thread B "ready", d.h. er könnte jetzt weiterlaufen. Da Core 2 gerade den Thread A mit niedrigerer Priorität bearbeitet, könnte man annehmen dass Core 2 zu dem Zeitpunkt sofort aufhört Thread A zu bearbeiten und statt dessen auf Thread B umschaltet.

    Genau das passiert aber nicht. Der Scheduler checkt nur ob der Thread der gerade "ready" geworden ist eine höhere Priorität hat, als der Thread der gerade auf dem aktuellen Core läuft. Da Core 1 aber Thread C ausführt, ist das nicht der Fall, und Core 1 schaltet nicht um. Und Core 2 bekommt davon einfach nichts mit.

    D.h. es dauert bis zum nächsten Scheduler-Interrupt auf Core 2 bis Thread B Rechenzeit bekommt.

    Man könnte das natürlich auch anders implementieren. Der Scheduler könnte einen Interrupt für den anderen Core triggern, damit dieser ohne Verzögerung umschalten kann. Das würde aber mehr Overhead erzeugen. Und daher macht es Windows nicht.


    Weiters kannst du pro Thread eine "affinity mask" setzen. Das ist einfach eine Bit-Maske, in der jedes Bit für einen Core steht. Und damit kontrollierst du auf welchen Cores ein Thread laufen darf. Siehe https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setthreadaffinitymask
    Wobei das mMn. in den wenigsten Fällen sinnvoll ist.

    Und dann gibt es noch die Möglichkeit Fibers zu verwenden. Das ist dann aber wieder kooperatives Multitasking.



  • @hustbaer sagte in MultiThreading Abstraktion CPU:

    z.B. kann man einen globalen Spin-Lock für die Thread-Liste machen. Wenn ein Core den Thread wechseln möchte schnappt er sich erstmal diesen Spin-Lock. Dann markiert er den alten Thread als "ready", sucht sich den Thread aus der als nächstes laufen soll, und markiert diesen dann als "running". Dann gibt er den Spin-Lock frei und schaltet auf den neuen Thread um.
    Dinge wie ein paar Cores schlafenlegen, wenn es zu wenig zu tun gibt, und sie wieder aufwecken wenn neue "ready" Threads dazukommen, sind vermutlich ein bisschen komplizierter. Aber ich denke da muss man nicht im Detail wissen wie das geht um das grundlegende Prinzip zu verstehen.

    aber genau das passiert auf dre HW oder? macht das schon das OS?

    @hustbaer sagte in MultiThreading Abstraktion CPU:

    Weiters kannst du pro Thread eine "affinity mask" setzen. Das ist einfach eine Bit-Maske, in der jedes Bit für einen Core steht. Und damit kontrollierst du auf welchen Cores ein Thread laufen darf. Siehe https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setthreadaffinitymask
    Wobei das mMn. in den wenigsten Fällen sinnvoll ist.
    Und dann gibt es noch die Möglichkeit Fibers zu verwenden. Das ist dann aber wieder kooperatives Multitasking.

    das ist interessant;)

    Danke Dir für die Ausführungen



  • @SoIntMan sagte in MultiThreading Abstraktion CPU:

    @hustbaer sagte in MultiThreading Abstraktion CPU:

    z.B. kann man einen globalen Spin-Lock für die Thread-Liste machen. Wenn ein Core den Thread wechseln möchte schnappt er sich erstmal diesen Spin-Lock. Dann markiert er den alten Thread als "ready", sucht sich den Thread aus der als nächstes laufen soll, und markiert diesen dann als "running". Dann gibt er den Spin-Lock frei und schaltet auf den neuen Thread um.
    Dinge wie ein paar Cores schlafenlegen, wenn es zu wenig zu tun gibt, und sie wieder aufwecken wenn neue "ready" Threads dazukommen, sind vermutlich ein bisschen komplizierter. Aber ich denke da muss man nicht im Detail wissen wie das geht um das grundlegende Prinzip zu verstehen.

    aber genau das passiert auf dre HW oder? macht das schon das OS?

    Das macht das OS.

    Aus Sicht der Hardware gibt es einfach N Cores, und jeder Core arbeitet halt sein Programm ab. D.h. sobald es mehr als N Threads geben kann, braucht man ein OS das eingreift um die Cores zwischen den Threads hin- und her zu schalten. Genau so wie es das OS auch macht wenn es nur einen Core gibt.

    Je nach CPU hast du mehr oder weniger mächtige Befehle um beim Umschalten zwischen den Thread zu helfen. Und je nach OS werden die dann verwendet oder auch nicht. In jedem Fall muss aber das OS den Thread-Wechsel durchführen. Der Unterschied ist bloss wie es das genau macht, wie viele CPU Instructions dazu nötig sind etc.



  • @hustbaer sagte in MultiThreading Abstraktion CPU:

    Das macht das OS.
    Aus Sicht der Hardware gibt es einfach N Cores, und jeder Core arbeitet halt sein Programm ab. D.h. sobald es mehr als N Threads geben kann, braucht man ein OS das eingreift um die Cores zwischen den Threads hin- und her zu schalten. Genau so wie es das OS auch macht wenn es nur einen Core gibt.
    Je nach CPU hast du mehr oder weniger mächtige Befehle um beim Umschalten zwischen den Thread zu helfen. Und je nach OS werden die dann verwendet oder auch nicht. In jedem Fall muss aber das OS den Thread-Wechsel durchführen. Der Unterschied ist bloss wie es das genau macht, wie viele CPU Instructions dazu nötig sind etc.

    aha.. ok verstehe, gibt es aber nicht CPU die selbst entscheiden können dinge zu parallelisieren ? dafür müsste ja dann schon ASM befehle vorgeladen sein nehm ich an!?



  • @SoIntMan Ja mittels Pipelining werden werden Teile von Befehlen gleichzeitig abgearbeitet. Und klarmuss man die laden.



  • @SoIntMan sagte in MultiThreading Abstraktion CPU:

    @hustbaer sagte in MultiThreading Abstraktion CPU:

    Das macht das OS.
    Aus Sicht der Hardware gibt es einfach N Cores, und jeder Core arbeitet halt sein Programm ab. D.h. sobald es mehr als N Threads geben kann, braucht man ein OS das eingreift um die Cores zwischen den Threads hin- und her zu schalten. Genau so wie es das OS auch macht wenn es nur einen Core gibt.
    Je nach CPU hast du mehr oder weniger mächtige Befehle um beim Umschalten zwischen den Thread zu helfen. Und je nach OS werden die dann verwendet oder auch nicht. In jedem Fall muss aber das OS den Thread-Wechsel durchführen. Der Unterschied ist bloss wie es das genau macht, wie viele CPU Instructions dazu nötig sind etc.

    aha.. ok verstehe, gibt es aber nicht CPU die selbst entscheiden können dinge zu parallelisieren ? dafür müsste ja dann schon ASM befehle vorgeladen sein nehm ich an!?

    Hm. Verstehe ich jetzt nicht. Mehrere Cores arbeiten natürlich parallel - ohne grosses Zutun des OS. (Also das OS muss sie einmal "anstarten", weil beim Boot halt nur ein Core arbeitet. Aber davon abgesehen laufen die dann selbständig sozusagen.) Nur jeder logische Core arbeitet für sich genommen nur einen Thread ab. D.h. wenn du z.B. 8 logische Cores hast, dann laufen halt 8 Threads parallel. Um eine unbegrenzte Anzahl an Threads abzuarbeiten, muss das OS eingreifen und umschalten.

    Davon abgesehen machen die CPUs intern natürlich einiges um Dinge zu parallelisieren. Aber "logisch" betrachtet bleibt es dabei dass ein Thread pro Core abgearbeitet wird.

    Zu den Dingen die CPUs intern machen gehören:

    Pipelining und out-of-order execution. Dabei werden mehrere Befehle des selben Threads parallel bzw. auch mit veränderter Reihenfolge abgearbeitet. Das geht z.B. wenn die Befehle nicht voneinander abhängig sind. Wenn z.B. ein Befehl die Register 1 und 2 addiert und der nächster Befehl die Register 3 und 4 multipliziert, dann können diese parallel abgearbeitet werden - bzw. es kann auch die Multiplikation vor der Addition ausgeführt werden.

    Davon abgesehen sind auch immer mehrere Befehle gleichzeitig "in Arbeit", in unterschiedlichen Stadien der Ausfürhung. Also z.B. ein Befehl wird gerade dekodiert, ein anderer wartet vielleicht gerade darauf dass einer seiner Inputs verfügbar wird (Speicheroperand, Output eines früheren Befehls etc.), noch ein anderer wartet darauf dass ein benötigtes Rechenwerk frei wird etc.

    Aber logisch gesehen ist und bleibt es ein Thread pro Core.

    Dann gibt es noch SMP aka. Hyper-Threading. Dabei bekommt ein physischer Core mehrere Sets an Registern, so dass sich zwei (oder mehr) unabhängige logische Cores pro physischem Core ergeben. Diese können logisch gesehen unabhängig voneinander arbeiten, als ob sie zwei komplett getrennte Cores wären. Intern teilen sie sich aber grosse Teile des Cores, üblicherweise die ganzen Recheneinheiten. Das ist aber "statisch" in dem Sinn dass es für das OS so aussieht als gäbe es z.B. 8 statt 4 Cores. Und jeder dieser logischen Cores arbeitet auch nur einen Thread ab, so lange das OS nicht eingreift.



  • @hustbaer sagte in MultiThreading Abstraktion CPU:

    Hm. Verstehe ich jetzt nicht. Mehrere Cores arbeiten natürlich parallel - ohne grosses Zutun des OS. (Also das OS muss sie einmal "anstarten", weil beim Boot halt nur ein Core arbeitet. Aber davon abgesehen laufen die dann selbständig sozusagen.) Nur jeder logische Core arbeitet für sich genommen nur einen Thread ab. D.h. wenn du z.B. 8 logische Cores hast, dann laufen halt 8 Threads parallel. Um eine unbegrenzte Anzahl an Threads abzuarbeiten, muss das OS eingreifen und umschalten.
    Davon abgesehen machen die CPUs intern natürlich einiges um Dinge zu parallelisieren. Aber "logisch" betrachtet bleibt es dabei dass ein Thread pro Core abgearbeitet wird.
    Zu den Dingen die CPUs intern machen gehören:
    Pipelining und out-of-order execution. Dabei werden mehrere Befehle des selben Threads parallel bzw. auch mit veränderter Reihenfolge abgearbeitet. Das geht z.B. wenn die Befehle nicht voneinander abhängig sind. Wenn z.B. ein Befehl die Register 1 und 2 addiert und der nächster Befehl die Register 3 und 4 multipliziert, dann können diese parallel abgearbeitet werden - bzw. es kann auch die Multiplikation vor der Addition ausgeführt werden.
    Davon abgesehen sind auch immer mehrere Befehle gleichzeitig "in Arbeit", in unterschiedlichen Stadien der Ausfürhung. Also z.B. ein Befehl wird gerade dekodiert, ein anderer wartet vielleicht gerade darauf dass einer seiner Inputs verfügbar wird (Speicheroperand, Output eines früheren Befehls etc.), noch ein anderer wartet darauf dass ein benötigtes Rechenwerk frei wird etc.
    Aber logisch gesehen ist und bleibt es ein Thread pro Core.
    Dann gibt es noch SMP aka. Hyper-Threading. Dabei bekommt ein physischer Core mehrere Sets an Registern, so dass sich zwei (oder mehr) unabhängige logische Cores pro physischem Core ergeben. Diese können logisch gesehen unabhängig voneinander arbeiten, als ob sie zwei komplett getrennte Cores wären. Intern teilen sie sich aber grosse Teile des Cores, üblicherweise die ganzen Recheneinheiten. Das ist aber "statisch" in dem Sinn dass es für das OS so aussieht als gäbe es z.B. 8 statt 4 Cores. Und jeder dieser logischen Cores arbeitet auch nur einen Thread ab, so lange das OS nicht eingreift.

    Vielen Dank, wirklich interessantes Thema... Danke Dir fürs viele Texten;)


Log in to reply