[C] while(1) / Concurrency *

Pagina: 1
Acties:

Acties:
  • 0 Henk 'm!

  • Mr_gadget
  • Registratie: Juni 2004
  • Laatst online: 14:54

Mr_gadget

C8H10N4O2 powered

Topicstarter
Ik zit nu te kijken naar het consumer / producer probleem, een standaard voorbeeld voor shared memory / message passing of het gebruik van locks. Echter wat ik een beetje vreemd vind aan het onderstaande voorbeeld is dat er een while(1) loop wordt gebruikt. Op het moment dat je procesA laat lopen gaat deze toch in een infite loop zitten waardoor B nooit aan de beurt komt toch?
Kon geen algemeen topic vinden dus maar even een apart topic.


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
01: data_type buffer[N];
02: int count = 0;
03: void processA() {
04: int i;
05: while( 1 ) {
06:    produce(&data);
07:    while( count == N );/*loop*/
08:           buffer[i] = data;
09:           i = (i + 1) % N;
10:          count = count + 1;
11:    }
12: }
13: void processB() {
14: int i;
15: while( 1 ) {
16:   while( count == 0 );/*loop*/
17:     data = buffer[i];
18:      i = (i + 1) % N;
19:     count = count - 1;
20:     consume(&data);
21:   }
22: }
23: void main() {
24: create_process(processA);
25: create_process(processB);
26: }

Acties:
  • 0 Henk 'm!

  • creator1988
  • Registratie: Januari 2007
  • Laatst online: 13:23
create_process voegt neem ik aan een functie toe aan een processmanager achtige oplossing, die dit op een aparte thread draait. Ergo: 2 threads, één die constant A draait, en één die constant B draait. Simultaan naast elkaar dus.

Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 19-09 21:24

.oisyn

Moderator Devschuur®

Demotivational Speaker

Ik vind het een beetje raar dat je het hebt over consumer/producer en locks, maar niet zelf op het idee komt dat processA en processB verschillende threads zijn die naast elkaar draaien...

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!

Verwijderd

*mompelt iets*

[ Voor 85% gewijzigd door Verwijderd op 01-07-2009 17:01 . Reden: *verkeerd gelezen* ]


Acties:
  • 0 Henk 'm!

  • Mr_gadget
  • Registratie: Juni 2004
  • Laatst online: 14:54

Mr_gadget

C8H10N4O2 powered

Topicstarter
Ah, zo natuurlijk :)

De andere voorbeelden waren ook single thread en het is een voorbeeld voor in een embedded systeem maar die kan natuurlijk ook wel 2 threads draaien :P

Acties:
  • 0 Henk 'm!

  • Chip.
  • Registratie: Mei 2006
  • Niet online
In de main wordt gewoon m.b.v. de create_process functie de functie processA() gestart als een geheel nieuw process. Daarna wordt processB() gestart als een geheel nieuw process. De while 1 zorgt gewoon dat het process niet geeindigd wordt...

Aan het einde zit je dus met 3 processen die parallel lopen van elkaar, processA, processB en de main.

Al hoewel ik eigenlijk zou denken dat de main afgesloten zou worden aangezien je eigenlijk verwacht dat op de laatste regel een functie voorkomt die kijkt of processA & processB zijn afgesloten en hijzelf dus ook kan stoppen.

[ Voor 17% gewijzigd door Chip. op 01-07-2009 17:02 ]


Acties:
  • 0 Henk 'm!

  • !null
  • Registratie: Maart 2008
  • Laatst online: 19-09 08:38
Proces/thread main zal daar niks maar te doen hebben, maar kan nog niet afsluiten (want dan zouden idd de twee threads doodgaan). Dat is gewoon een thread voor het systeem wat in idle gaat en nooit meer iets te doen krijgt. Maar dat is wat ik denk. Wat ook zou kunnen, en effectief op hetzelfde neer zou komen, is dat de main thread daar dus afgelopen is, en daarna ook niet meer bestaat. Maar dan bestaat het proces en de context (de variabelen etc) nog wel, waarmee de threads A en B kunnen werken. Misschien kan iemand vertellen wat er precies gebeurd in deze situatie. Ik weet wel hoe het werkt etc, maar niet zo specifiek.

[ Voor 6% gewijzigd door !null op 01-07-2009 17:25 ]

Ampera-e (60kWh) -> (66kWh)


Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 19-09 21:24

.oisyn

Moderator Devschuur®

Demotivational Speaker

Een conforme C implementatie zal het proces afsluiten, aangezien einde uit main() een exit() betekent.

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!

  • creator1988
  • Registratie: Januari 2007
  • Laatst online: 13:23
Wellicht zit er in de processmanager iets dat de main thread idle houdt.

Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 19-09 21:24

.oisyn

Moderator Devschuur®

Demotivational Speaker

Ik zou deze code sowieso niet willen runnen. Geen enkele variabele is gedeclareerd als volatile, en bovendien is het niet gedefinieerd dat het incrementen of decrementen van een int atomair is. Ik denk dan ook dat het meer pseudo-code is dan daadwerkelijke C code. Zo niet dan is het rijp voor [alg] Slechtste programmeervoorbeelden deel 4

[ Voor 11% gewijzigd door .oisyn op 01-07-2009 17:55 ]

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!

  • _JGC_
  • Registratie: Juli 2000
  • Nu online
Sowieso mist de main() functie, dus een op zichzelf staand C programma is het niet. Beetje vaag programmeervoorbeeld, ik had eerder iets verwacht met pthread.

Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 19-09 21:24

.oisyn

Moderator Devschuur®

Demotivational Speaker

Beter lezen. Regel 23. Overigens returnt ie een void, ipv de verplichte int :)

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!

  • _JGC_
  • Registratie: Juli 2000
  • Nu online
Ah, die had ik gemist in de brij ja. Behalve dat ie een void teruggeeft neemt ie ook geen argc en argv opties. Weet niet in hoeverre die optioneel zijn, maar ik voeg ze standaard altijd toe.

Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 19-09 21:24

.oisyn

Moderator Devschuur®

Demotivational Speaker

Ze zijn niet verplicht :)

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!

  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Lijkt wel een Tanenbaum-pseudo-code voorbeeld van hoe het niet moet :)

Begin alsjeblieft niet de discussie over het gebruik van volatile in C... :P

Acties:
  • 0 Henk 'm!

  • !null
  • Registratie: Maart 2008
  • Laatst online: 19-09 08:38
Ja logisch eigenlijk dat ie er dan gewoon mee kapt.
Maar wat bedoel je in deze met atomair?

Ampera-e (60kWh) -> (66kWh)


Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 19-09 21:24

.oisyn

Moderator Devschuur®

Demotivational Speaker

Dat de operatie niet opdeelbaar is en er dus niets anders uit kan komen. Een RISC CPU zal typisch eerst de waarde inlezen, dan verhogen, dan wegschrijven. Als twee threads dat tegelijkertijd doen dan kan dit optreden:
count = 0
A: lees count (count = 0)
A: verhoog count (count = 1)
B: lees count (count = 0)
B: verhoog count (count = 1)
B: schrijf count (count = 1)
A: schrijf count (count = 1)

Count eindigt dan op 1, terwijl twee threads 'm verhoogd hebben. Als een increment atomair was, dan werd het zo:
A: lees count (0)
A: verhoog count (1)
A: schrijf count (1)
B: lees count (1)
B: verhoog count (2)
B: schrijf count (2)

A en B kunnen natuurlijk omdraaien, maar er zal nooit een actie kunnen komen tussen de acties van A of tussen de acties van B. De increment is dan atomair.

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!

  • !null
  • Registratie: Maart 2008
  • Laatst online: 19-09 08:38
Oh dat probleem, ja ik snap het probleem. Wellicht een domme vraag, maar hoe wil je die 2e geschetste situatie realiseren in dit voorbeeld?

Ampera-e (60kWh) -> (66kWh)


Acties:
  • 0 Henk 'm!

  • Enfer
  • Registratie: Februari 2004
  • Laatst online: 18-09 16:32
Concurrency is dan het keyword waar je op moet zoeken. Zo heb je onder andere de mutex, waarmee je acties af kunt schermen die door twee threads tegelijk kunnen uitgevoerd worden, in dit voorbeeld dan net niet mogelijk, maar het volgende kan wel:

code:
1
2
3
4
5
6
7
8
9
10
11
int i = 0;
Mutex m;

void add(){
 m.acquire();
 i++;
 m.release();
}

process_start( add );
process_start( add );

offtopic:
Complete pseudocode dit trouwens ;)


Als je een mutex wilt locken / acquiren, dan wordt ervoor gezorgd dat maar 1 proces dit mag doen. Als een tweede proces ook de add methode aanroept, terwijl een eerste proces de mutex al gelockt heeft, zal het tweede proces gaan wachten bij de m.acquire() totdat proces 1 hem weer vrijgeeft.

Zie bijvoorbeeld ook: Wikipedia: Semaphore (programming)

[ Voor 76% gewijzigd door Enfer op 01-07-2009 18:58 ]


Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 19-09 21:24

.oisyn

Moderator Devschuur®

Demotivational Speaker

Locking primitives zoals mutices en semaphores zijn wellicht wat overkill voor een simpele increment op een variabele. Vrijwel alle CPU's ondersteunen wel een manier om bepaalde zaken atomair te regelen (logisch - anders waren locking primitives niet eens te implementeren ;)). Meestal biedt het OS wel dergelijke faciliteiten, zoals InterlockedIncrement() op Windows. Op een x86 CPU vertaalt dat zich naar een 'lock inc [count]'. Voor een single-threaded, single-CPU omgeving waarin je puur pre-emptive multitasking hebt is natuurlijk genoeg om interrupts gewoon even uit te zetten.

[ Voor 15% gewijzigd door .oisyn op 01-07-2009 19:22 ]

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!

  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Lock doet meestal niet heel veel tegenwoordig; cache coherency regels garanderen al dat het goed gaat op een multi-core/cpu, mits aligned en <= machine size. Niet dat je het dan niet moet gebruiken hoor, je kan beter gewoon portable blijven (bovendien negeren moderne intels het indien niet nodig), maar als side-info. Ik was toevallig gisteren bezig met het testen van performance van atomics.
Hmm...dat blijkt nu dus na testen niet waar te zijn. Vreemd.

Oh, ok, inc is verder niet atomic intern, alleen reads en writes. Zo zie je, dat zelfs een enkele assembly instructie niet atomic is... wel logisch eigenlijk

[ Voor 22% gewijzigd door Zoijar op 01-07-2009 21:45 ]


Acties:
  • 0 Henk 'm!

  • !null
  • Registratie: Maart 2008
  • Laatst online: 19-09 08:38
Sorry, ik was niet geheel duidelijk, ik bedoelde meer een praktijk oplossing. Want de theorie beheers ik goed (vrij goed les in gehad) qua semaphoren en concurrency en alle ellende die mis kan gaan. Maar om eerlijk te zijn heb ik vrijwel geen praktijk ervaring bij het oplossen hiervan. Zo had ik inderdaad ook een semaphore bedacht, maar hoe ga je die dan gebruiken? Moet je dan een system call doen, of zijn er instructies in C die zich altijd goed laten vertalen naar het betreffende platform. Je kan ook wel zelf een semaphore erin knutselen maar dan zit je nog met hetzelfde atomaire probleem natuurlijk :P

Maar .oisyn, uit jou stukje begrijp ik dat je een system call moet doen welke zich vertaalt in bepaalde instructies (die lock inc ..), waarom kan dit niet vanuit C gerealiseerd worden? (dus buiten het OS om)
Ik snap dat echte semaphoren etc platform specifiek zouden kunnen zijn, komt zo'n lock instructie niet op andere architecturen zoals ARM of MIPS voor?

Ampera-e (60kWh) -> (66kWh)


Acties:
  • 0 Henk 'm!

  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Dit is voor posix, maar misschien maakt het wat duidelijker hoe het in de praktijk werkt:

http://www.yolinux.com/TU...TutorialPosixThreads.html

De instructies bestaan op de meeste architecturen wel, maar hebben andere opcodes etc, dus het is erg platform specifiek. Vandaar dat het meestal door het OS wordt geregeld. C op zichzelf heeft geen standaard functies hiervoor (anders zou een platform zonder die instructies nooit C kunnen implementeren; vroeger waren er genoeg platformen die support misten). Nieuwe talen/frameworks hebben meestal wel direct ondersteuning, zoals C#. Ook de toekomstige nieuwe versie van C++ heeft standaard support, waar op het moment libraries zoals boost-threads worden gebruikt ( http://www.boost.org/doc/libs/1_39_0/doc/html/thread.html ).

Die libraries gebruiken meestal OS system calls waar de locking wordt geregeld. Tegenwoordig komt het meer in de mode om user-space synchronisatie via atomic operations the doen, wat men ook vaak wel "lockless" implementaties noemt. Dan krijg je dit soort C++ functies:

C++:
1
2
3
4
5
6
7
8
9
10
11
    void operator++()
    {
        __asm__
        (
            "lock\n\t"
            "incl %0":
            "+m"( value_ ): // output (%0)
            : // inputs
            "cc" // clobbers
        );
    }

voor een atomic increment. Of op windows heb je user-space functies in libraries als InterlockedIncrement() die hetzelfde doen.

[ Voor 32% gewijzigd door Zoijar op 02-07-2009 11:08 ]


Acties:
  • 0 Henk 'm!

  • !null
  • Registratie: Maart 2008
  • Laatst online: 19-09 08:38
Die c++ library, dat is dus een library welke het betreffende OS op de juiste manier aanspreken, welk OS het ook is? Of moet ik het zien als een library welke zich goed laat compileren naar de juiste instructies, voor de architecturen die daar support voor hebben? En dus niet compileerbaar is op architectuur zonder deze isntructies.
Maakt het voor het gebruik en als het goed is voor het resultaat niks uit natuurlijk, maar gewoon benieuwd.


Oh ik zie nu je edit...

[ Voor 3% gewijzigd door !null op 02-07-2009 11:08 ]

Ampera-e (60kWh) -> (66kWh)


Acties:
  • 0 Henk 'm!

  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

!null schreef op donderdag 02 juli 2009 @ 11:08:
Die c++ library, dat is dus een library welke het betreffende OS op de juiste manier aanspreken, welk OS het ook is? Of moet ik het zien als een library welke zich goed laat compileren naar de juiste instructies, voor de architecturen die daar support voor hebben? En dus niet compileerbaar is op architectuur zonder deze isntructies.
Maakt het voor het gebruik en als het goed is voor het resultaat niks uit natuurlijk, maar gewoon benieuwd.
Ja, beide zijn mogelijk. Meestal vertalen threading libraries naar de juiste OS system calls, voor een aantal veel gebruikte OS-en. Het desbetrefende OS zal op zijn beurt intern bepaalde machine instructies gebruiken om dit te implementeren. Andere "lockless" libraries zullen zelf de implementatie regelen via machine code, en zijn dus afhankelijk van de machine ipv het OS. Maar dit is relatief nieuw. De klassieke manier is dat het OS die functies implementeert, zoals posix threads.

Volgens mij werkt die code die je postte trouwens gewoon als je ipv "count = count + 1;" en "count = count - 1;" een atomic increment en decrement maakt. Dan wel via InterlockedIncrement/Decrement, danwel via assembly lock inc/dev instructies. Voorbeeld van een lockless PCB.

[ Voor 11% gewijzigd door Zoijar op 02-07-2009 11:19 ]


Acties:
  • 0 Henk 'm!

  • !null
  • Registratie: Maart 2008
  • Laatst online: 19-09 08:38
Ik heb geen code gepost ;)
Maar als zo'n lib dus het zelf doet buiten het OS om (die relatief nieuwe manier zoals je zegt) dan gaat deze dus waarschijnlijk zelf een stukje assembly erin gooien, wat dus architectuur afhankelijk is. Niet echt een goeie richting lijkt me.

Ampera-e (60kWh) -> (66kWh)


Acties:
  • 0 Henk 'm!

  • .oisyn
  • Registratie: September 2000
  • Laatst online: 19-09 21:24

.oisyn

Moderator Devschuur®

Demotivational Speaker

!null schreef op donderdag 02 juli 2009 @ 10:53:
Maar .oisyn, uit jou stukje begrijp ik dat je een system call moet doen welke zich vertaalt in bepaalde instructies (die lock inc ..), waarom kan dit niet vanuit C gerealiseerd worden? (dus buiten het OS om)
Dat kan prima in C, alleen niet in standaard C. Meestal ondersteunt de compiler zogenaamde intrinsics - functies die je aanroept die in feite direct naar assembly instructies vertalen. strlen() of memcpy() zijn goede voorbeelden van dergelijke functies.

Het OS biedt meestal wat functionaliteit aan voor atomaire operaties. De compiler implementatie herkent die functies en kan dat direct vertalen naar assembly instructies. Het mooie hiervan is dat als je een compiler gebruikt die dat niet kan, je alsnog gewoon die functies kunt gebruiken. Maar als je een compiler gebruikt die het wel kan performt het een stuk beter. Zonder dat je de code hoeft aan te passen :)
Ik snap dat echte semaphoren etc platform specifiek zouden kunnen zijn, komt zo'n lock instructie niet op andere architecturen zoals ARM of MIPS voor?
De x86 is een CISC architectuur. Daarmee kun je in een enkele instructie bijv. een waarde bij een waarde dat in het geheugen staat optellen. (Met een RISC CPU heb je daar meestal 3 instructies voor nodig (een read, een add en een write. Overigens zijn hedendaagse x86 CPUs intern ook gewoon RISC chips, en worden de instructies eerst vertaald naar RISC microcode instructies.). Echter, de geheugenmodules zelf hebben die logica niet, dus de waarde zal alsnog eerst uit het geheugen gehaald moeten worden, vervolgens opgehoogd, en daarna weer weggeschreven. Met de 'lock' prefix zorg je ervoor dat dat geheugenadres wordt gelocked door de memory controller zodat een andere CPU niet ook wat met dat geheugen kan doen voordat de lock weer is vrijgegeven. Een soort hardwarematige mutex dus :).

Voor dingen als mutices en semaphores heb je meestal test-and-set of load-link/store-conditional. Daarmee is het mogelijk om read-modify-write operaties uit te voeren, waarbij de write in feite alleen maar lukt als de waarde op dat geheugenadres niet in de tussentijd is veranderd.

Voor een mutex heb je dan bijv. een variabele die aangeeft welke thread de mutex owned, of 0 als hij vrij is. Een thread die een lock doet, leest de waarde. Als die niet 0 is dan gaat ie in slaapstand. Als hij wel 0 is dan probeert hij zijn eigen thread id in het geheugenadres te zetten. Is de waarde in tussentijd verandert, dan probeert hij het van voor af aan.

De x86 CPU heeft hiervoor de compareexchange functie, die een geheugenwaarde vergelijkt met een bepaalde waarde, en indien het gelijk is die waarde vervangt met een nieuwe waarde. Het resultaat in het register is de oude waarde, onafhankelijk van of het schrijven gelukt is of niet. De win32 API functie hiervoor heet InterlockedCompareExchange.

C++:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct mutex
{
    int owner;
}

void lock(mutex * m)
{
    int mytid = GetCurrentThreadId();
    // mytid blijven proberen te schrijven totdat er geen owner is.
    while(InterlockedCompareExchange(&m->owner, mytid, 0));
    //                                            ^    ^----- waarde waarmee owner wordt vergeleken
    //                                            \---------- nieuwe waarde van 'owner' als de oude waarde overeenkomt
}

void unlock(mutex * m)
{
    int mytid = GetCurrentThreadId();
    // alleen een 0 schrijven als de huidige owner onze eigen thread is.
    if (InterlockedCompareExchange(&m->owner, 0, mytid) != mytid)
        error("you didn't own the lock");
}


Nou zijn dit natuurlijk busy-waits en niet 'fair'. Een goed OS zal een thread in slaapstand plaatsen en de thread toevoegen aan de queue van de mutex, zodat als ie geunlocked wordt hij de volgende wachtende thread de lock geeft.

[ Voor 3% gewijzigd door .oisyn op 02-07-2009 14:36 ]

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!

  • !null
  • Registratie: Maart 2008
  • Laatst online: 19-09 08:38
Duidelijk verhaal, bedankt.
In de functie lock zie ik inderdaad een busy-wait, wat je bedoelt met die laatste opmerking is me niet duidelijk in detail. Ok een queue, dat is duidelijk, dat zorgt voor een eerlijkere verdeling/volgorde. Maar dat ie hem dan verplicht in slaapstand plaatst.. is dat wel wenselijk? Dit betekent dat je je thread verplicht moet laten blocken/slapen op bijvoorbeeld een InterlockedCompareExchange(). Dat zal vaak genoeg heel logisch zijn omdat je verder niks te doen hebt, en het is natuurlijk efficienter dan een busy-wait methode. Maar ik kan me situaties bedenken dat je wil weten of het mislukt, en dan dus wat anders kan gaan doen (of fouten genereren oid). Dan zou je bij die blocking methode dus geen keus hebben? Lijkt me dat je dan eerder deadlocks (bij een complexere situatie) of 'starvation' hebt.

Ampera-e (60kWh) -> (66kWh)


Acties:
  • 0 Henk 'm!

  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Meestal kan je aangeven of je wilt blocken of niet, en hoe lang maximaal, bij systeem calls dan. Dit geldt niet voor interlocked/atomic operaties, die zitten een niveau dichter bij de hardware. Een interlocked operatie blocked nooit (lang), alleen misschien een aantal nanoseconden op hardware niveau. Een system call kan een willekeurige tijd blocken, tot minuten aan toe.

zo'n while-loop op een atomic heet overigens een spin-lock. Spinlocks zijn erg handig als je verwacht nooit lang te hoeven wachten, waar 'lang' dan meestal meer dan zeg een milliseconden is. Het laten slapen van een thread via het OS en die later wakker maken is een erg dure operatie. De thread/process context moet op je CPU worden vervangen en later opnieuw geladen, plus de overhead van je software thread/process scheduler. In dat geval is het vaak beter om een milliseconde busy-wait te wachten, dan alle overhead van een sleep/wake-up op je te nemen.

[ Voor 43% gewijzigd door Zoijar op 02-07-2009 14:06 ]


Acties:
  • 0 Henk 'm!

  • !null
  • Registratie: Maart 2008
  • Laatst online: 19-09 08:38
Wat is dan het verschil tussen busy-wait en een spin-lock? Dat het bij een busy-wait om andere dingen kan gaan dan een lock? Bijvoorbeeld semaphoren oid (in principe ook iets wat atomair werkt).

Oh weer een edit. Ja dat snap ik inderdaad, je wil niet te veel context switchen. Al is een context switch van een thread natuurlijk weer minder zwaar dan van een proces (toch?). Maar ik snap de afweging.

(en sorry dat ik dit topic heb gekaapt, maar ik vind het een mooie gelegenheid om wat praktijkoplossingen te zien etc, anders zakt die theorie ook alleen maar weg gezien ik op mijn werk voorlopig ook niet met threading in aanraking kom)

[ Voor 56% gewijzigd door !null op 02-07-2009 14:23 ]

Ampera-e (60kWh) -> (66kWh)


Acties:
  • 0 Henk 'm!

  • Zoijar
  • Registratie: September 2001
  • Niet online

Zoijar

Because he doesn't row...

Een spinlock is een lock geimplemeteerd met busy-wait; busy-wait is een term om aan te tonen dat je herhaaldelijk een conditie test in een loop. Je bent dus aan het wachten (wait) maar de CPU is wel bezig (busy) :)

Acties:
  • 0 Henk 'm!

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

H!GHGuY

Try and take over the world...

Zoijar schreef op donderdag 02 juli 2009 @ 14:00:
zo'n while-loop op een atomic heet overigens een spin-lock. Spinlocks zijn erg handig als je verwacht nooit lang te hoeven wachten, waar 'lang' dan meestal meer dan zeg een milliseconden is. Het laten slapen van een thread via het OS en die later wakker maken is een erg dure operatie. De thread/process context moet op je CPU worden vervangen en later opnieuw geladen, plus de overhead van je software thread/process scheduler. In dat geval is het vaak beter om een milliseconde busy-wait te wachten, dan alle overhead van een sleep/wake-up op je te nemen.
Spinlocks kun je ook beter vermijden in userspace. Enkel binnen de kernel kan deze enig nut hebben.

In Linux is een spinlock bij een single-processor systeem trouwens geen echte spinlock, maar niets meer dan het uitschakelen van interrupts. Want als jij die code uitvoert op 1 CPU dan kan niemand anders de spinlock hebben (want anders was je daar zelf ook nooit gekomen).
Op een multi-processor Linux is een spinlock wél een echte spinlock. Er kan namelijk een 2de processor bezig zijn binnen de scope van de spinlock, en zoals Zoijar aangeeft: spinlocks enkel gebruiken voor heel korte operaties. Dus dan verbrand je hopelijk maximaal enkele 10-100-tallen instructies terwijl je busy-loopt op de spinlock. Als je alles wat je binnen een spinlock doet trouwens zo klein mogelijk houdt dan is de kans dat je moet spinnen op een SMP systeem ook zoveel kleiner.

Binnen user-space kan je echter problemen krijgen met spinlocks. Je neemt een hoge prioriteit thread en een lage prioriteit thread waarbij deze laatste de spinlock heeft op een single-CPU systeem.
Als je hoge-prioriteit thread nu actief wordt en begint te spinnen kan het zijn dat je lage-prio thread geen processor tijd krijgt om de spinlock vrij te geven. Je krijgt dus een vorm van priority inversion die op een naïeve scheduler kan leiden tot een deadlock.

Systemen zoals windows en Linux gaan (by default dacht ik) echter af en toe processen een hogere prioriteit geven om starvation te voorkomen. Maar echt geinig is een spinlock in usercode niet.

In linux is het ook zo dat een mutex eigenlijk een futex is. Waarbij in userspace gebruik wordt gemaakt van
atomaire instructies om zo te beslissen of de mutex moet blokken. In dit geval wordt een futex-call gedaan waarna het process blokkeert.

ASSUME makes an ASS out of U and ME

Pagina: 1