Ik wil een applicatie maken die informatie van meerdere websites, webservices en/of rss-feeds ophaalt en combineert. Nu wil ik voorkomen dat ik op een enkele webserver teveel requests achter elkaar uitzet (ook wel "hammeren" genoemd), en om de requests min of meer gelijkmatig te verdelen over de webservers wil ik een scheduler maken die garandeert dat er niet te snel achter elkaar requests op dezelfde server worden afgeschoten.
Deze scheduler loopt in een aparte thread, en beheert een X aantal job (of "worker") threads die het van de ingeschedulde jobs moet voorzien, nl. het opvragen en parsen van een pagina/feed/whatever. Wanneer een job thread tijdelijk niets te doen heeft, zal deze thread blocken totdat het een nieuwe job krijgt van de scheduler. Dit voorkomt dat er continue nieuwe threads worden aangemaakt en opgeruimd.
Globaal werkt de scheduler als volgt: de scheduler wacht totdat er een job thread vrijkomt. Dan zal de scheduler kijken of er op dit moment al een job is die uitgevoerd mag worden. Zo niet, dan zal de scheduler wachten totdat een van de aanwezige jobs wel uitgevoerd mag worden, of totdat er een nieuwe job wordt toegevoegd die meteen uitgevoerd kan worden. Als de job aan de job thread is meegegeven, dan zal het weer wachten totdat er een job thread vrijkomt (als die er al niet is).
Goed en wel, maar het punt waar ik moeite mee had, is het thread-safe doorgeven van een job aan een job thread. Hieronder heb ik schematisch mijn oplossing weergegeven, waarbij ik per job thread 2 mutexen gebruik om zowel de scheduler te kunnen laten wachten op een vrije job thread, en een job thread kan laten wachten op een job.
Nu vraag ik me af: kan dit niet makkelijker?
Onderstaand stappenplan gaat uit van een Scheduler Thread met 1 Job Thread; in het echt zal de Scheduler Thread bij stap a) de Idle Mutexen van meerdere Job Threads in de gaten houden totdat er een van vrij komt (d.m.v. WaitForAny()), en met deze Job Thread de rest van de stappen doorlopen.
a) Hier is de Job Thread nog bezig met een job. Het bezit de Idle Mutex, en zolang de job thread deze heeft zal de Scheduler Thread blocken.
1) De Job Thread geeft de Idle mutex vrij, om aan de Scheduler Thread aan te geven dat het klaar is met zijn job.
b) De Job Thread wacht op de Help mutex, zodat deze Job Thread geblockt blijft totdat de scheduler het een job toewijst. Wanneer de Scheduler een job heeft voor deze thread, zet hij dit als een Job object op een member variabele van de Job Thread.
2) De Scheduler thread geeft de Help Mutex vrij, zodat de Job Thread weer running wordt en weet dat het een job toegewezen heeft gekregen.
c) De Job Thread wacht totdat het de Idle Mutex terugkrijgt.
3) De Scheduler geeft de Idle mutex vrij zodat de Job Thread deze weer kan pakken.
d) De Job Thread heeft de Idle mutex gepakt zodat het als "bezet" gemarkeerd is; de Scheduler thread zal blocken wanneer het probeert de Idle mutex van deze Job Thread te pakken om een nieuwe job toe te wijzen.
4) Nu de Job Thread de Idle Mutex heeft, kan het de Help Mutex teruggeven aan de Scheduler thread en beginnen aan zijn Job.
e) Door het pakken van de Help Mutex weet de Scheduler thread zeker dat de Job Thread de Idle Mutex heeft, en kan weer wachten totdat de Idle Mutex vrijkomt.
Het over en weer geven van de mutexen is volgens mij nodig omdat je nooit zeker weet dat wanneer de ene thread een mutex vrijgeeft, dat de andere thread deze meteen pakt.
Maar denk ik te ingewikkeld?
Pfoe, dat is een flinke lap tekst geworden
Deze scheduler loopt in een aparte thread, en beheert een X aantal job (of "worker") threads die het van de ingeschedulde jobs moet voorzien, nl. het opvragen en parsen van een pagina/feed/whatever. Wanneer een job thread tijdelijk niets te doen heeft, zal deze thread blocken totdat het een nieuwe job krijgt van de scheduler. Dit voorkomt dat er continue nieuwe threads worden aangemaakt en opgeruimd.
Globaal werkt de scheduler als volgt: de scheduler wacht totdat er een job thread vrijkomt. Dan zal de scheduler kijken of er op dit moment al een job is die uitgevoerd mag worden. Zo niet, dan zal de scheduler wachten totdat een van de aanwezige jobs wel uitgevoerd mag worden, of totdat er een nieuwe job wordt toegevoegd die meteen uitgevoerd kan worden. Als de job aan de job thread is meegegeven, dan zal het weer wachten totdat er een job thread vrijkomt (als die er al niet is).
Goed en wel, maar het punt waar ik moeite mee had, is het thread-safe doorgeven van een job aan een job thread. Hieronder heb ik schematisch mijn oplossing weergegeven, waarbij ik per job thread 2 mutexen gebruik om zowel de scheduler te kunnen laten wachten op een vrije job thread, en een job thread kan laten wachten op een job.
Nu vraag ik me af: kan dit niet makkelijker?
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| Scheduler thread : Job Thread : IdleMutex HelpMutex : IdleMutex HelpMutex | | |X| : |X| | a) | | |X| : |X| | | | |X| : |X| | | |/_______|X|______(1)_____|X| | |X|\ |X| : | | |X| |X| : | | b) |X| |X| Set Job on_\ | | |X| |X| Job Thread / | | |X| |X| : | | | |X| |X|______(2)______|________\| | |X| | : | /|X| c) |X| | : | |X| |X| | : | | |X| |X|_________|_______(3)____\| | |X| | | : /|X| |X| d) | | : |X| |X| | | | : |X| |X| | | |/_____(4)_____|X|________|X| | |X|\ : |X| | e) | |X| : |X| | | |X| : |X| | Legenda: | = Mutex is niet in het bezit van de thread. |X| = Mutex is in het bezit van de thread. | | = Thread wacht totdat het de mutex krijgt --> = Thread geeft mutex vrij |
Onderstaand stappenplan gaat uit van een Scheduler Thread met 1 Job Thread; in het echt zal de Scheduler Thread bij stap a) de Idle Mutexen van meerdere Job Threads in de gaten houden totdat er een van vrij komt (d.m.v. WaitForAny()), en met deze Job Thread de rest van de stappen doorlopen.
a) Hier is de Job Thread nog bezig met een job. Het bezit de Idle Mutex, en zolang de job thread deze heeft zal de Scheduler Thread blocken.
1) De Job Thread geeft de Idle mutex vrij, om aan de Scheduler Thread aan te geven dat het klaar is met zijn job.
b) De Job Thread wacht op de Help mutex, zodat deze Job Thread geblockt blijft totdat de scheduler het een job toewijst. Wanneer de Scheduler een job heeft voor deze thread, zet hij dit als een Job object op een member variabele van de Job Thread.
2) De Scheduler thread geeft de Help Mutex vrij, zodat de Job Thread weer running wordt en weet dat het een job toegewezen heeft gekregen.
c) De Job Thread wacht totdat het de Idle Mutex terugkrijgt.
3) De Scheduler geeft de Idle mutex vrij zodat de Job Thread deze weer kan pakken.
d) De Job Thread heeft de Idle mutex gepakt zodat het als "bezet" gemarkeerd is; de Scheduler thread zal blocken wanneer het probeert de Idle mutex van deze Job Thread te pakken om een nieuwe job toe te wijzen.
4) Nu de Job Thread de Idle Mutex heeft, kan het de Help Mutex teruggeven aan de Scheduler thread en beginnen aan zijn Job.
e) Door het pakken van de Help Mutex weet de Scheduler thread zeker dat de Job Thread de Idle Mutex heeft, en kan weer wachten totdat de Idle Mutex vrijkomt.
Het over en weer geven van de mutexen is volgens mij nodig omdat je nooit zeker weet dat wanneer de ene thread een mutex vrijgeeft, dat de andere thread deze meteen pakt.
Maar denk ik te ingewikkeld?
Pfoe, dat is een flinke lap tekst geworden