[C] Voorkomen van deadlock in programma structuur

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • CurlyMo
  • Registratie: Februari 2011
  • Laatst online: 19:27
Inleiding
Na lang zelf geprobeerd te hebben een oplossing te vinden voor onderstaande probleem heb ik toch besloten om jullie om raad te vragen.

Afbeeldingslocatie: http://i.imgur.com/3k5NulQ.png

De belangrijkste taken van mijn programma zijn het ontvangen en verzenden van data.

- Het doel van de ontvanger is het verwerken van interrupts tot betekenisvolle data. Het doel van de zender is het verzenden van die data. Voor het op te lossen probleem maakt het niet uit of dit nu het verzenden en ontvangen van IR data is of 433.92Mhz enz.
- Het doel van de broadcaster is om alle acties die er in het programma gebeuren (via een API) te delen met externe programma's.
- Aanvullend biedt mijn programma ook de mogelijkheid van eventing en biedt het een ingebouwde webGUI aan.

Om alles zonder al te veel vertraging te laten lopen werkt het geheel van een aantal threadsafe queues. Dat betekent dat de ontvanger, zender en broadcaster logica telkens uit twee functie bestaan (zie verderop). In het geval van de ontvanger is er één functie die de ontvanger lijst vult en één functie die de ontvanger lijst uitleest. Dit heb ik toen zo geïmplementeerd zodat ik er zeker van kon zijn dat ik geen belangrijke informatie mis aan de ontvangst kant mocht de broadcaster te lang bezig zijn met zijn taak. Uiteindelijk heb ik die logica doorgetrokken in alle functionaliteit die in dit schema te zien is.

Om het probleem goed te doorgronden is het dus goed om te weten dat mijn programma nu gebruik maakt van de volgende threads:
- receive_queue / receive_process
- send_queue / send_process
- broadcast_queue / broadcast_process
- events_queue / events_process
- webserver_queue / webserver_process

Zoals gebruikelijk word de consistentie van de queue's bewaakt door locks:
code:
1
2
3
4
[lock]
vul queue
[unlock]
[signal]


Probleem
Zoals ik in de titel aangeef ontstaat hier het risico van een deadlock. Een gangbare flow van het programma is als volgt.

code:
1
Receiver > Broadcast > Event > Sender > Broadcast

Er wordt een bepaalde code ontvangen, die wordt door de broadcaster doorgestuurd naar de eventing functionaliteit. De gebruiker heeft bepaald dat er aan de hand van deze code een andere code moet worden verzonden. De zender functie verteld vervolgens weer aan de broadcaster dat er iets verzonden is zodat er daaropvolgende weer een event kan plaatsvinden en ook de GUIs de kans krijgen om de verandering te visualiseren.

Zoals te zien is ontstaat het probleem op het moment dat de eerste broadcast functie nog bezig is op het moment dat hij weer opnieuw aangeroepen wordt. De oude lock is nog niet vrijgegeven terwijl er nieuwe wordt aangevraagd.

In de basis van zenden / ontvangen en broadcasten werkte alles prima. Nu echter deze problemen zich voordoen kom ik tot de conclusie dat deze implementatie zo niet houdbaar is voor de huidige complexiteit.

Tussenoplossing
Om het geheel toch werkend te krijgen is alle functionaliteit die afhankelijk is van de broadcaster nu via een socket verbinding aan het geheel geknoopt. Hierdoor draaien de GUIs en de eventing functionaliteit eigenlijk als aparte programma's naast het hoofdprogramma.

Zelf bedacht alternatief
Andere ideeën die ik had was het creëren van een soort event-driven-functionaliteit / observer pattern. De receiver laat weten dat er een code is ontvangen, deze triggered een functie die nagaat welke geregistreerde functies er geïnteresseerd zijn in deze informatie en voert deze functies uit.

Het probleem hiervan is dat dit (naar mijn idee) alleen werkt wanneer deze functies ook weer threaded worden gestart, omdat je anders opnieuw het risico loopt dat een trage GUI de receiver kan blokkeren. Elke thread krijgt dan een kopie van de data zodat je ook niet aan een threadsafe queue vast zit. Zou ik per event een nieuwe thread moeten starten, dan worden er in het ergste geval een tientallen keren per seconde threads gestart en weer gestopt. Dat is dus ook niet ideaal, maar zou in theorie wel alternatief kunnen zijn voor mijn probleem.

Andere oplossingen?
En op dat punt kwam ik er niet meer uit en besloot ik maar weer eens zelf een vraag te stellen :)

Sinds de 2 dagen regel reageer ik hier niet meer


Acties:
  • 0 Henk 'm!

  • BoringDay
  • Registratie: Maart 2009
  • Laatst online: 13-05 21:49
Wanneer iets gereed is dan pas een event aanroepen? (kortom toepassen van callbacks)

Acties:
  • 0 Henk 'm!

  • CurlyMo
  • Registratie: Februari 2011
  • Laatst online: 19:27
Dat gebeurt al.

Sinds de 2 dagen regel reageer ik hier niet meer


Acties:
  • 0 Henk 'm!

  • donzz
  • Registratie: Maart 2006
  • Laatst online: 09-09 23:12
Zo min mogelijk doen tijdens de lock. Dus lock, item uit queue halen, unlock, item afhandelen. Op die manier kunnen andere threads in de queue schrijven tijdens het afhandelen, en worden die items afgehandeld zodra het huidige item klaar is.

alles kan kapot; beter dat ik het nu test dan dat er straks iemand komt klagen


Acties:
  • 0 Henk 'm!

  • CurlyMo
  • Registratie: Februari 2011
  • Laatst online: 19:27
Het probleem is alleen dat zoals je hier kunt zien de lock nog actief is zodra opnieuw de broadcast aangeroepen wordt:
code:
1
Receiver > Broadcast > Event > Sender > Broadcast

Sinds de 2 dagen regel reageer ik hier niet meer


Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 03:42

.oisyn

Moderator Devschuur®

Demotivational Speaker

Het probleem is dat je broadcaster niet reentrant is. Wat ik echter niet snap is waarom de lock gehouden wordt terwijl het volgende systeem wordt aangeroepen. Wat is überhaupt het nut van de queues als je alles ad hoc gaat processen?

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Acties:
  • 0 Henk 'm!

  • CurlyMo
  • Registratie: Februari 2011
  • Laatst online: 19:27
.oisyn schreef op zondag 28 juni 2015 @ 22:38:
Wat ik echter niet snap is waarom de lock gehouden wordt terwijl het volgende systeem wordt aangeroepen.
Om de queue consistent te houden:
code:
1
2
3
4
Lock
Functie
Queue stap verder
Unlock
Wat is überhaupt het nut van de queues als je alles ad hoc gaat processen?
De queues gebruik ik om de functies asynchroon van elkaar te houden. Er kunnen bijvoorbeeld meerdere receiver functies zijn die allemaal data willen doorsturen naar de broadcaster. Dan is het fijn als deze functies asynchroon van de broadcaster hun data kwijt kunnen voordat de broadcaster het verwerkt heeft.

Sinds de 2 dagen regel reageer ik hier niet meer


Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 03:42

.oisyn

Moderator Devschuur®

Demotivational Speaker

Waarom niet:
C:
1
2
3
4
lock();
v = pop();
unlock();
process(v);

?

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Acties:
  • 0 Henk 'm!

  • Oyster
  • Registratie: Januari 2003
  • Niet online

Oyster

Prince

Geen threads gebruiken maar een pool van single event loops instantieren die je zo kort als mogelijk en non-blocking houdt. Blocking acties kan je eventueel naar een workerpool delegeren.

Maar ik snap je probleem niet helemaal. Maar dat kan er ook aan liggen dat hetlaat is en ik waarschijnlijk de helft lees. Wat bedoel je met lock op een functie? Lock op resources, oké. Maar wat is er mis met de broadcasterqueue en timeout? Ik zie in deze constructie maar weinig reden tot deadlock in softwarescope. Misschien maak je een abstracte denkfout.
CurlyMo schreef op zondag 28 juni 2015 @ 22:50:
[...]

Om de queue consistent te houden:
code:
1
2
3
4
Lock
Functie
Queue stap verder
Unlock



[...]

De queues gebruik ik om de functies asynchroon van elkaar te houden. Er kunnen bijvoorbeeld meerdere receiver functies zijn die allemaal data willen doorsturen naar de broadcaster. Dan is het fijn als deze functies asynchroon van de broadcaster hun data kwijt kunnen voordat de broadcaster het verwerkt heeft.
Voor asynchroon in combinatie met threads zal je steeds harder moeten bidden voor meer geluk op een goede werking naar mate het programma langer uitgevoerd wordt. :p

Volgens mij Is dit probleem op iedere denkbare manier onderhand al jaren getackeld trouwens. Nergens voorbeelden of een OSS implementatie kunnen vinden?

[ Voor 93% gewijzigd door Oyster op 29-06-2015 02:48 ]


Acties:
  • 0 Henk 'm!

  • CurlyMo
  • Registratie: Februari 2011
  • Laatst online: 19:27
.oisyn schreef op maandag 29 juni 2015 @ 01:53:
Waarom niet:
C:
1
2
3
4
lock();
v = pop();
unlock();
process(v);

?
Wat ook een cruciale denkfout was is dat ik inderdaad de lock véél te lang in stand hield. Ik weet dat je een lock neerzet om te voorkomen dat twee threads tegelijk in een queue gaan zitten werken, maar wist ook dat deze lock zo kort mogelijk moet zijn. Dat deed ik nu niet door ook verschillende functies uit te voeren alvorens ik de lock weer ophief. Dat komt ook omdat ik in plaats van een kopie van v de pointer naar v doorstuurde naar de andere functies. Daardoor zorgde ik ervoor dat de functies die afhankelijk zijn van de pointer naar v ook gelocked werden gehouden. Dus ik denk dat ik met deze kleine aanpassing mijn kern probleem al opgelost had :)

Door jullie vragen ben ik wel weer een stapje verder en denk ik een meer duurzame manier te hebben gevonden in mijn eigen oplossing, maar dan goed geïmplementeerd (threadpool + event-driven):

De receiver laat weten dat er een code is ontvangen (REASON_RECEIVED_CODE). De broadcaster heeft aangegeven geïnteresseerd te zijn in deze informatie. De broadcaster zal vervolgens uitgevoerd worden. De broadcaster geeft daarna weer aan klaar te zijn (REASON_BROADCASTED). De event functies en GUIs hebben zichzelf weer op dat event geregistreerd en worden vervolgens uitgevoerd.

Nu dan de oplossing voor het threads probleem. Eerst startte ik voor elke functie die getriggered werd een aparte thread en stopte ik hem deze daarna weer. Dat is dus geen optie. Wat ik nu ga proberen te doen is standaard een X aantal worker threads te starten. Zodra dus nu de receiver aangeeft code te hebben ontvangen, dan kijkt mijn threadpool of er worker threads beschikbaar zijn en laat deze de broadcast functie uitvoeren. Als alle workers druk zijn, dan kan er automatisch een nieuwe thread worden gestart, en als een worker thread te lang niks heeft gedaan, dan kan deze worden gestopt.

Op deze manier reduceer ik het aantal threads in mijn programma flink en zorg ik ook voor de nodige abstractie in mijn code. Zo hoeft de broadcaster zelf niet meer te weten dat er GUIs en eventing geïnteresseerd zijn in zijn uitkomsten. Het enige wat het hoeft te doen is aan te geven dat hij informatie heeft. Ook verander ik het geheel zodat er makkelijk een kopie van de data doorgestuurd kan worden naar afhankelijke functies. Daarmee kan ik ook een aantal threadsafe queues weghalen.

Sinds de 2 dagen regel reageer ik hier niet meer


Acties:
  • 0 Henk 'm!

  • H!GHGuY
  • Registratie: December 2002
  • Niet online

H!GHGuY

Try and take over the world...

Als aanvulling, er bestaat een programmeurs-gezegde: "never call unknown code while holding a lock".
En dat is exact wat je deed door die functie aan te roepen.
CurlyMo schreef op maandag 29 juni 2015 @ 09:41:
[...]
Nu dan de oplossing voor het threads probleem. Eerst startte ik voor elke functie die getriggered werd een aparte thread en stopte ik hem deze daarna weer. Dat is dus geen optie. Wat ik nu ga proberen te doen is standaard een X aantal worker threads te starten. Zodra dus nu de receiver aangeeft code te hebben ontvangen, dan kijkt mijn threadpool of er worker threads beschikbaar zijn en laat deze de broadcast functie uitvoeren. Als alle workers druk zijn, dan kan er automatisch een nieuwe thread worden gestart, en als een worker thread te lang niks heeft gedaan, dan kan deze worden gestopt.

Op deze manier reduceer ik het aantal threads in mijn programma flink en zorg ik ook voor de nodige abstractie in mijn code. Zo hoeft de broadcaster zelf niet meer te weten dat er GUIs en eventing geïnteresseerd zijn in zijn uitkomsten. Het enige wat het hoeft te doen is aan te geven dat hij informatie heeft. Ook verander ik het geheel zodat er makkelijk een kopie van de data doorgestuurd kan worden naar afhankelijke functies. Daarmee kan ik ook een aantal threadsafe queues weghalen.
Ik betwijfel dat je met die strategie het aantal threads omlaag haalt.
Denk er even over na: je haalt enkel de kost van threads-starten/stoppen omlaag voor de eerste X threads (waarbij X het aantal workers is wat je aanhoudt). Als je, telkens alle X threads bezig zijn, alsnog een nieuw thread start, komt dit in max aantal threads nog altijd overeen met 1 thread per functie.

Het idee van threadpools is meestal dat je een maximum aan threads hebt (bvb: 1 per hyperthreaded core) en dat je dan werk in een queue stopt. Die threads halen dan telkens werk uit die queue om uit te voeren. Belangrijk is dan wel om die threads niet (of zo min mogelijk, of zo deterministisch+kort mogelijk) te laten slapen. Roep dus liefst geen (blocking) I/O functies aan (tenzij mss asynchroon).

Dan heb je altijd nog verbeteringen (bvb CPU-local queues met work-stealing) en andere fijne zaken, maar daar zou ik me nog even niets van aantrekken. Ik heb de indruk dat het voorlopig zo al complex genoeg is voor je.

ASSUME makes an ASS out of U and ME


Acties:
  • 0 Henk 'm!

  • CurlyMo
  • Registratie: Februari 2011
  • Laatst online: 19:27
H!GHGuY schreef op maandag 29 juni 2015 @ 09:57:
Als aanvulling, er bestaat een programmeurs-gezegde: "never call unknown code while holding a lock".
En dat is exact wat je deed door die functie aan te roepen.
Goede tip
Als je, telkens alle X threads bezig zijn, alsnog een nieuw thread start, komt dit in max aantal threads nog altijd overeen met 1 thread per functie.
Toen ik zei thread-pool impliceerde ik ook dat er een maximaal aantal worker threads actief mogen zijn.

In de nieuwe situatie zijn er wel een aantal verplichte threads die één ding doen. Dat zijn de receivers die de interrupts uitlezen. Dat kan dus tegelijk een 433.92Mhz thread zijn en tweede IR thread. Daarnaast is dat ook de webserver die requests moet afhandelen, asynchroon van de rest van mijn programma.

Al het werk daartussen kan dan afgehandeld worden door de worker threads. In de basis komt dat dus neer op op zo'n 4 tot 6 threads terwijl ik in de oude situatie al standaard zo'n 12+ threads had lopen.

Sinds de 2 dagen regel reageer ik hier niet meer


Acties:
  • 0 Henk 'm!

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 15:26
Heb je ueberhaubt vastgesteld dat je meerdere threads nodig hebt? En waarvoor je dan die threads nodig hebt? Of ben je er meteen van uit gegaan dat ze nodig zijn?

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


Acties:
  • 0 Henk 'm!

  • CurlyMo
  • Registratie: Februari 2011
  • Laatst online: 19:27
Bedoel je meerdere threads in de threadpool of überhaupt threads in mijn programma?

Sinds de 2 dagen regel reageer ik hier niet meer


Acties:
  • 0 Henk 'm!

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 15:26
CurlyMo schreef op maandag 29 juni 2015 @ 11:25:
Bedoel je meerdere threads in de threadpool of überhaupt threads in mijn programma?
Meerdere threads voor het systeem dat je hier beschrijft

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


Acties:
  • 0 Henk 'm!

  • CurlyMo
  • Registratie: Februari 2011
  • Laatst online: 19:27
farlane schreef op maandag 29 juni 2015 @ 11:48:
Meerdere threads voor het systeem dat je hier beschrijft.
De belangrijkste functionaliteit is het uitlezen van interrupts of van de seriële poort. Daarbij kunnen er één of meerdere interrupts tegelijk uitgelezen worden. De informatie die uit deze interrupts gehaald wordt, moet uiteindelijk verwerkt worden verderop in het programma.

Wat ik met de threads + queues wil voorkomen is dat het verwerken van de informatie van interrupt 1 (bijv. 433.92Mhz) het uitlezen en verwerken van interrupt 2 (bijv. IR) stagneert.

Daarnaast moet het mogelijk zijn om de data die via de interrupts uitgelezen is ook weer te verzenden. Het kan dus zijn dat terwijl interrupt 1 (bijv. 433.92Mhz) aan het ontvangen is er tegelijkertijd een IR signaal wordt verzonden

Verderop wordt bijvoorbeeld ook de verwerkte data uit de interrupts gedeeld via een webGUI. Opnieuw moet het niet zo zijn dat een slechte HTTP / websockets verbinding het ontvangst gedeelte stil legt. Dat zijn dus de plaatsen waar ik het risico loop of blokkerende IO verbindingen.

Dat is de reden dat ik bepaalde functionaliteit in aparte threads laat draaien. Je opmerking of ik niet alles te makkelijk in threads ben gaan doen klopt. Daar kom ik ook langzamerhand achter. Vandaar dat ik nu goed aan het kijken ben wat echt threaded moet gebeuren, wat er afgehandeld kan worden door een threadpool, en wat er helemaal niet threaded hoeft te gebeuren.

[ Voor 5% gewijzigd door CurlyMo op 29-06-2015 11:59 ]

Sinds de 2 dagen regel reageer ik hier niet meer


Acties:
  • 0 Henk 'm!

  • EddoH
  • Registratie: Maart 2009
  • Niet online

EddoH

Backpfeifengesicht

Als je interrupts gebruikt dan is juist de afhandeling hiervan gegarandeerd. Ik snap niet zo goed hoe je de koppeling threads <-> interrupts maakt.
Een interrupt gebruik je voor kritische delen waarbij er (vaak vanaf HW) data binnenkomt die gegarandeerd afgehandeld moet worden. Een interrupt blokkeert de code executies zodat de ISR(Interrupt Service Routine) uitgevoerd kan worden. Deze ISR zet dan de data in een buffer, zodat je die in de rest van je programma verder kan gaan verwerken.
Het voordeel hiervan is juist dat je geen polling hoeft te doen en zorgen hoeft te maken of je wel snel genoeg bent voor je inkomende data (simpel gezegd).

Acties:
  • 0 Henk 'm!

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 15:26
Begrijp ik het goed dat je platform een embedded ding is met een (RT)OS erop? Vergeet niet dat threads/tasks worden gebruikt om de *latency* bij het verwerken van gegevens/messages *kan* verkleinen, maar het zal je nooit gaan helpen bij het sneller verwerken van de interrupts zelf, dus het ontvangen van serieele gegevens "tegelijk" met het verzenden van een IR signaal kan alleen als je processor dat ook daadwerkelijk aankan.

Meestal is het voldoende om je berichten op interrupt/DMA in een buffer te plaatsen en die te verwerken als je daar tijd voor hebt : het plaatsen van dat bericht in een queue (als dat nodig is) zou misschien gewoon vanuit je hoofdthread kunnen waardoor je helemaal geen locks nodig hebt.

Uiteindelijk kan het pollen van een aantal statemachines die aan je berichtenstromen gekoppeld zijn veel sneller zijn dan het aanmaken van allerlei taken/threads waardoor je plotsklaps met synchronisatieuitdagingen, taskswitching etc zit.


Een beetje wat EddoH ook zegt eigenlijk.....

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


Acties:
  • 0 Henk 'm!

  • CurlyMo
  • Registratie: Februari 2011
  • Laatst online: 19:27
Mijn platform is een Raspberry Pi en aanverwanten :)

Sinds de 2 dagen regel reageer ik hier niet meer


Acties:
  • 0 Henk 'm!

  • CurlyMo
  • Registratie: Februari 2011
  • Laatst online: 19:27
Nog even voortbordurend op de eerste vraag. De threadpool en het event-driven systeem werkt goed. Ook het periodiek pollen van verschillende services gaat nu ook via de threadpool.

Nu een meer abstracte vraag om nog wat meer van jullie te leren. We hebben het al over interrupts gehad en hoe die allemaal vanuit één enkele thread af te handelen, daar kijk ik later nog even naar. Voor nu vroeg ik me af hoe je dat doet met socket verbindingen. Weer een korte uitleg:

Mijn programma draait tot drie verschillende socket verbindingen:
1. TCP socket voor externe clients.
2. UDP (multicast) socket voor ssdp discovery
3. TCP socket voor de webserver.

Nu weet ik dat ik allerhande verbindingen met een socket kan afhandelen via een select. Hoe implementeer je echter het verwerken van verschillende socket verbindingen in zo min mogelijk threads? Nu start in voor elke verbinding een aparte thread (los van de threadpool) zodat ze asynchroon afgehandeld kunnen worden.

Sinds de 2 dagen regel reageer ik hier niet meer


Acties:
  • 0 Henk 'm!

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 15:26
Associeer elke socket die je met select polled met een handler functie ( mbv een struct bv ) en roep die aan als select aangeeft dat er wat aan de hand is met die socket.

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


Acties:
  • 0 Henk 'm!

  • The Eagle
  • Registratie: Januari 2002
  • Laatst online: 18:16

The Eagle

I wear my sunglasses at night

Ik gooi hem even over een hele andere boeg: kijk even naar https://flume.apache.org
Volgens mij doet dat exact wat je wilt cq probeert te bereiken :)

Al is het nieuws nog zo slecht, het wordt leuker als je het op zijn Brabants zegt :)


Acties:
  • 0 Henk 'm!

  • Soultaker
  • Registratie: September 2000
  • Laatst online: 19:15
Flume gaat never nooit fatsoenlijk draaien op een Raspberry Pi. ;)

Acties:
  • 0 Henk 'm!

  • SPee
  • Registratie: Oktober 2001
  • Laatst online: 12:56
Volgens mij wordt het probleem veroorzaakt door blocking queues.
Door gebruik te maken van non-blocking queues (b.v. ringbuffer), dan heb je dat probleem van locks niet. :)

Hoewel een java library (en .Net kloon) is de paper achter de Disruptor library wel interessante leesvoer, ongeacht de taal.

let the past be the past.


Acties:
  • 0 Henk 'm!

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 15:26
Tja, ik heb natuurlijk niet de details van de applicatie maar als ik het zo hoor heb je maar 1 thread nodig, en hoeven de queues helemaal niet thread safe te zijn.

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


Acties:
  • 0 Henk 'm!

  • CurlyMo
  • Registratie: Februari 2011
  • Laatst online: 19:27
SPee schreef op zondag 05 juli 2015 @ 17:23:
Volgens mij wordt het probleem veroorzaakt door blocking queues.
Door gebruik te maken van non-blocking queues (b.v. ringbuffer), dan heb je dat probleem van locks niet. :)

Hoewel een java library (en .Net kloon) is de paper achter de Disruptor library wel interessante leesvoer, ongeacht de taal.
Dat probleem heb ik al getackeld m.b.v. de threadpool.
farlane schreef op zondag 05 juli 2015 @ 16:16:
Associeer elke socket die je met select polled met een handler functie ( mbv een struct bv ) en roep die aan als select aangeeft dat er wat aan de hand is met die socket.
Hoe ik met de data om ga die beschikbaar komt weet ik wel. Mijn vraag is anders. Een versimpeld stukje (pseudo)code:
code:
1
2
3
4
5
6
socket TCP
bind 5000
listen
FD_ZERO
FD_SET
select

Dat werkt natuurlijk allemaal prima. De select geeft netjes aan wanneer er inkomend verkeer is op TCP socket 5000. Maar in de huidige situatie heb ik (versimpeld) dit:

Thread 1
code:
1
2
3
4
5
6
socket TCP
bind 5000
listen
FD_ZERO
FD_SET
select


Thread 2
code:
1
2
3
4
5
6
socket TCP
bind 5001
listen
FD_ZERO
FD_SET
select


Thread 1 handelt dus al het verkeer voor TCP 5000 af en thread 2 voor TCP 5001. Mijn vraag is of een constructie zoals dit niet mogelijk is:
code:
1
2
3
4
5
6
7
socket TCP
bind 5001
bind 5002
listen
FD_ZERO
FD_SET
select

Een situatie dus waarin één select het inkomende verkeer van TCP 5000 en 5001 afhandelt. Idealiter zou één select het verkeer van TCP 433 en TCP 80 (webserver), TCP 5000 (API) en UDP 1900 (SSDP) af handelen.

Sinds de 2 dagen regel reageer ik hier niet meer


Acties:
  • 0 Henk 'm!

  • H!GHGuY
  • Registratie: December 2002
  • Niet online

H!GHGuY

Try and take over the world...

Tuurlijk, dan roep je gewoon zoveel keer FD_SET op als je FDs hebt.
Je wil ook misschien eens kijken naar poll/epoll - deze zijn efficienter.

ASSUME makes an ASS out of U and ME


Acties:
  • 0 Henk 'm!

  • CurlyMo
  • Registratie: Februari 2011
  • Laatst online: 19:27
Dus als ik het goed begrijp kijken select / FD_* functies alleen naar de FD's ongeacht de bron? Dat kunnen dus FD's vanuit verschillende sockets zijn.

Sinds de 2 dagen regel reageer ik hier niet meer


Acties:
  • 0 Henk 'm!

  • H!GHGuY
  • Registratie: December 2002
  • Niet online

H!GHGuY

Try and take over the world...

CurlyMo schreef op zondag 05 juli 2015 @ 20:25:
Dus als ik het goed begrijp kijken select / FD_* functies alleen naar de FD's ongeacht de bron? Dat kunnen dus FD's vanuit verschillende sockets zijn.
Tuurlijk. Moest de kernel de afkomst en geschiedenis van alles moeten bijhouden, dan zou ze zo traag als windows worden O-)

En dan nog: probeer het. Als je systeem het niet plezant vindt, krijg je heus een error code. Het is geen electronica, je sloopt er je PC niet mee ;)

ASSUME makes an ASS out of U and ME


Acties:
  • 0 Henk 'm!

  • Soultaker
  • Registratie: September 2000
  • Laatst online: 19:15
H!GHGuY schreef op zondag 05 juli 2015 @ 20:22:
Tuurlijk, dan roep je gewoon zoveel keer FD_SET op als je FDs hebt.
Je wil ook misschien eens kijken naar poll/epoll - deze zijn efficienter.
Maakt echt geen reet uit als het om zo weinig filedescriptors gaat. ;)

Heeft iemand trouwens al opgemerkt dat mutexen locken in signal handlers onveilig is? Dat lijkt me eerlijk gezegd het grootste probleem met de huidige configuratie.

Acties:
  • 0 Henk 'm!

  • CurlyMo
  • Registratie: Februari 2011
  • Laatst online: 19:27
H!GHGuY schreef op zondag 05 juli 2015 @ 22:31:
Tuurlijk. Moest de kernel de afkomst en geschiedenis van alles moeten bijhouden, dan zou ze zo traag als windows worden O-)

En dan nog: probeer het. Als je systeem het niet plezant vindt, krijg je heus een error code. Het is geen electronica, je sloopt er je PC niet mee ;)
Ik ben normaal gesproken van het proberen, maar een select op FD's vanuit meerdere bronnen is niet in 3 seconde geschreven. Net allemaal wat complexer (voor mij). Op een of andere manier heb ik mezelf dan ook wijsgemaakt dat je geen select kan zetten op FD's vanuit meerdere bronnen. Waar ik dat vandaan heb weet ik niet. Vandaar ook dit topic :)
Soultaker schreef op zondag 05 juli 2015 @ 22:43:
Heeft iemand trouwens al opgemerkt dat mutexen locken in signal handlers onveilig is? Dat lijkt me eerlijk gezegd het grootste probleem met de huidige configuratie.
Daar ben ik bekend mee, en volgens mij doe ik dat ook niet?

Ik ben nu mijn applicatie aan het herschrijven zodat de threadpool en de event-driven logica overal wordt doorgevoerd. Volgende stap is de socket verbindingen goed afhandelen. Met dat eerste heb ik me echter al genoeg werk op de hals gehaald, dus die sockets zullen nog wel even duren :)




Dan kom ik op het volgende punt. Tijdens de start van mijn programma gebeuren (versimpeld o.a.) de volgende dingen:
1. De temperatuur van de CPU wordt elke 10 seconden uitgelezen.
2. Het CPU en RAM gebruik wordt elke 3 seconde opgevraagd.
3. Er wordt verbinding gelegd met een NTP server om de juiste tijd te achterhalen mocht het apparaat een incorrecte tijd hebben (zoals Raspberry Pi's zonder RTC of incompetente gebruikers).

Nu bestonden alle drie de functies voorheen uit een aparte thread ;w Nu worden deze functies afgehandeld door de threadpool. Maar nu het nieuwe probleem. Het verbinding maken met de NTP servers is een blocking IO. Het opvragen van de NTP tijd gebeurt als volgt:
code:
1
2
3
4
socket
connect
sendto
recvfrom

De connect, sendto en recvfrom functies hebben elk een timeout van drie seconde. De thread uit de pool die de NTP synchronisatie afhandelt zou dus nooit meer dan 6 seconden moeten blokkeren (connect slaagt op de grens van 3 seconde, maar de sendto of recvfrom faalt met een timeout van 3 seconde), maar dat is eigenlijk al immens lang.

Zou ik op dat moment een threadpool gebruiken met maar één thread. Dan ligt de uitvoer van de andere functies in de pool voor max. 6 seconde stil. Er zijn niet veel plaatsen in mijn programma waar dit kan gebeuren, maar wel een paar. Een andere is bijvoorbeeld het uitlezen van de WeatherUnderground API. Opnieuw gaat het om een vergelijkbare blocked IO socket verbinding.


farlane schreef op zondag 05 juli 2015 @ 19:22:
Tja, ik heb natuurlijk niet de details van de applicatie maar als ik het zo hoor heb je maar 1 thread nodig, en hoeven de queues helemaal niet thread safe te zijn.
Aan de hand van deze informatie dus de volgende vragen.

1. Dat je de FD's van meerdere socket (server) verbindingen af te handelen zijn binnen één thread is me duidelijk geworden. Maar zover ik het begrijp moet er wel een toegewijde thread zijn die in een blocking select loop wacht op input van de FD's en wanneer een FD aangeeft data te hebben de threadpool aan de gang gezet kan worden. Dat betekent dus dat wanneer ik de threadpool één thread geef en ook de socket FD's in één thread afhandel, ik al twee threads heb lopen. Klopt?

2. Is er een manier (en ja ik heb opnieuw flink gezocht) waarmee je een socket client verbinding goed kan integreren in een threadpool? Nu probeer ik dus tijdens het starten de tijd te synchroniseren via NTP, maar tijdens dat synchroniseren ligt de threadpool stil (in geval van één thread).

Ik leer zeer veel van jullie tips en suggesties! :Y Met name een paar zelfbedachte en niet onderbouwde paradigma's die ik aan het doorprikken ben met jullie hulp.

De titel van dit topic mag ondertussen wel veranderd worden in een meer algemenere titel aangezien ik graag wat dieper de materie in wil duiken via deze vragen (die niet altijd meer met deadlocks te maken hebben.

Sinds de 2 dagen regel reageer ik hier niet meer


Acties:
  • 0 Henk 'm!

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 15:26
1. De temperatuur van de CPU wordt elke 10 seconden uitgelezen.
2. Het CPU en RAM gebruik wordt elke 3 seconde opgevraagd.
3. Er wordt verbinding gelegd met een NTP server om de juiste tijd te achterhalen mocht het apparaat een incorrecte tijd hebben (zoals Raspberry Pi's zonder RTC of incompetente gebruikers).
Als er een taak voor een apart thread in aanmerking zou komen is dat nr 3, ik zie niet in waarom die andere in aparte threads zouden moeten : hoe lang duurt het uitlezen van een CPU temperatuur nou helemaal?
Als je multithreading met deze granulariteit gaat doen kost het locken en threadswitchen meer tijd dan de call zelf volgens mij. ( maar daarvoor zou je eigenlijk moeten gaan profilen )
1. Dat je de FD's van meerdere socket (server) verbindingen af te handelen zijn binnen één thread is me duidelijk geworden. Maar zover ik het begrijp moet er wel een toegewijde thread zijn die in een blocking select loop wacht op input van de FD's en wanneer een FD aangeeft data te hebben de threadpool aan de gang gezet kan worden. Dat betekent dus dat wanneer ik de threadpool één thread geef en ook de socket FD's in één thread afhandel, ik al twee threads heb lopen. Klopt?
Lift er aan, select kun je ook een (kleine) timeout meegeven. Je moet alleen wel oppassen dat je niet een busyloop creeert als er helemaal niets te doen is.
2. Is er een manier (en ja ik heb opnieuw flink gezocht) waarmee je een socket client verbinding goed kan integreren in een threadpool? Nu probeer ik dus tijdens het starten de tijd te synchroniseren via NTP, maar tijdens dat synchroniseren ligt de threadpool stil (in geval van één thread).
Het wachten op een antwoord van de NTP server hoeft natuurlijk niet blocking te gebeuren, tenzij je een library gebruikt die niet anders kan. Een timeout van 3s kun je ook in je user code doen vanuit een statemachine.
Een andere methode zou zijn om het resultaat niet in een queue te zetten maar meteen te verwerken in dezelfde thread als waarin je het NTP request doet.

[toevoeging]
Mbt je select(...) vragen : je kunt zelfs filedescriptors pollen die heel iets anders voorstellen : in een bepaalde applicatie poll ik op deze manier tegelijkertijd sockets en seriele poorten op readability/ontvangen data.

[ Voor 5% gewijzigd door farlane op 06-07-2015 09:27 ]

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


Acties:
  • 0 Henk 'm!

  • CurlyMo
  • Registratie: Februari 2011
  • Laatst online: 19:27
farlane schreef op maandag 06 juli 2015 @ 09:25:
Als er een taak voor een apart thread in aanmerking zou komen is dat nr 3, ik zie niet in waarom die andere in aparte threads zouden moeten : hoe lang duurt het uitlezen van een CPU temperatuur nou helemaal?
Als je multithreading met deze granulariteit gaat doen kost het locken en threadswitchen meer tijd dan de call zelf volgens mij. ( maar daarvoor zou je eigenlijk moeten gaan profilen )
Zoals ik ook al aangaf is dat nu ook mijn conclusie.
Ligt er aan, select kun je ook een (kleine) timeout meegeven. Je moet alleen wel oppassen dat je niet een busyloop creeert als er helemaal niets te doen is.
Wat ik nu doe is een aantal threads maken die periodiek uitgevoerd moeten worden. Die twee CPU functies zijn daar voorbeelden van. Er wordt dus aan het einde van main een loop gestart die en het programma open houdt en kijkt welke functie er uitgevoerd moeten worden. Voorbeeld:
code:
1
2
3
4
loop
  sleep
  functie interval is bereikt
    laat functie uitvoeren door threadpool

Begrijp ik het goed dat ik in jouw suggestie dus de sleep door een select met timeout kan vervangen zodat die loop zowel mijn periodieke threads afhandelt en tegelijk mijn FD's?
Het wachten op een antwoord van de NTP server hoeft natuurlijk niet blocking te gebeuren, tenzij je een library gebruikt die niet anders kan. Een timeout van 3s kun je ook in je user code doen vanuit een statemachine.
Misschien snap ik het niet of schiet mijn kennis te kort, maar een non-blocking IO is toch altijd afhankelijk van een timeout die aangeeft hoe lang de IO maximaal op antwoord mag wachten? In mijn geval is die timeout dus 3 seconde. Lang genoeg voor de hele verbinding om te reageren. Zou ik die timeout veel korter maken (0.5 seconde) moet ik dan niet de server veel vaker pollen om er zeker van te zijn dat er geen connectie mogelijk is?
Een andere methode zou zijn om het resultaat niet in een queue te zetten maar meteen te verwerken in dezelfde thread als waarin je het NTP request doet.
Dat is ook wat er nu gebeurt.
[toevoeging]
Mbt je select(...) vragen : je kunt zelfs filedescriptors pollen die heel iets anders voorstellen : in een bepaalde applicatie poll ik op deze manier tegelijkertijd sockets en seriele poorten op readability/ontvangen data.
Dat begrijp ik nu ook. Nu snap ik ook waarom het afhandelen van interrupts en seriële verbindingen niet elk in een aparte thread hoeft (waar we het eerder over hadden). Het zijn allemaal gewoon FD's :) Wat ik nog wel wil testen is in hoeverre het spammen van één interrupt (ruis) invloed heeft op de afhandeling van mijn andere FD's.

Sinds de 2 dagen regel reageer ik hier niet meer


Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 03:42

.oisyn

Moderator Devschuur®

Demotivational Speaker

CurlyMo schreef op maandag 06 juli 2015 @ 10:26:
Misschien snap ik het niet of schiet mijn kennis te kort, maar een non-blocking IO is toch altijd afhankelijk van een timeout die aangeeft hoe lang de IO maximaal op antwoord mag wachten?
Nee, non-blocking is precies wat het zegt: het blokkeert de thread niet. Als er een antwoord is dan zal dat worden teruggeven, en anders zal er iets van een statuscode oid worden teruggegeven die zegt dat het antwoord nog niet beschikbaar is.

Give a man a game and he'll have fun for a day. Teach a man to make games and he'll never have fun again.


Acties:
  • 0 Henk 'm!

  • CurlyMo
  • Registratie: Februari 2011
  • Laatst online: 19:27
Maar zodra de thread gestopt is voordat het antwoord beschikbaar was, dan stopt toch ook de vraag?

Edit:
Of is zoiets mogelijk:

Functie call 1:
code:
1
2
3
open socket
select op FD 1 (time out)
hou connectie open


Functie call 2:
code:
1
2
select op FD 1 (nog steeds timeout)
hou connectie open


Functie call 3:
code:
1
2
3
4
select op FD 1 (data beschikbaar)
sendto
recvfrom
sluit connectie FD 1


Zojuist getest en dat is dus wat er bedoeld wordt met non-blocking IO :D

[ Voor 70% gewijzigd door CurlyMo op 06-07-2015 12:06 ]

Sinds de 2 dagen regel reageer ik hier niet meer


Acties:
  • 0 Henk 'm!

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 15:26
Inderdaad maar dan zo:
Functie call 1:
code:
1
2
3
4
open socket
sendto
select op FD 1 (time out)
hou connectie open


Functie call 2:
code:
1
2
select op FD 1 (nog steeds timeout)
hou connectie open


Functie call 3:
code:
1
2
3
select op FD 1 (data beschikbaar)
recvfrom
sluit connectie FD 1


En vergeet niet een timeout in te bouwen : als de andere kant helemaal niet reageert moet je statemachine ook tot een einde komen.
Wat ik nog wel wil testen is in hoeverre het spammen van één interrupt (ruis) invloed heeft op de afhandeling van mijn andere FD's.
Tenzij je bezig bent in kernel space, is het niet 'echt' een interrupt wat het event genereert (indirect wel). In de seriele driver wordt ontvangen data vanuit waarschijnlijk een hardware buffer gekopieerd naar een buffer die is geassocieerd met de poort. De select zal readable teruggeven als er 1 of meerdere bytes in deze kernel buffer staan. Het zou niet te doen zijn om bij elke ontvangen byte user code aan te gaan roepen : je processor zou dan waarschijnlijk binnen de kortste keren door zijn enkels gaan.

[ Voor 42% gewijzigd door farlane op 06-07-2015 16:01 ]

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.

Pagina: 1