Hur tycker ni jag ska göra detta lib trådsäkert

Permalänk
Avstängd

Hur tycker ni jag ska göra detta lib trådsäkert

Jag har ett litet Pub/sub Lib för SignalR, de viktigaste funktionerna finns där men innan jag pushar det till 1.0 status måste jag se till att den klarar av samtidiga operationer då flera användare kan subscriba samtidigt eller pub's itererar över sub's samtidigt som en annan tråd anropar sub.

Koden finns här
https://github.com/AndersMalmgren/SignalR.EventAggregatorProx...

Jag har valt att projicera subscriptions på två aggregatrötter, dels på event typ och dels på connectionId.

Det är 3 metoder som skriver / läser till dessa

  • Subscribe

  • Unsubscribe

  • UnsubscribeConnection

Medans en som bara läser

  • Handle

Jag skrev ett litet test för att testa att koden är trådsäker
https://github.com/AndersMalmgren/SignalR.EventAggregatorProx...

Jag har även uppdaterat den koden med lite benchmark kod så jag kan testa prestandan, men den koden har degenererat under tiden jag testat olika metoder så måste skriva om den innan jag kan commita den.

Den mest säkra metoden är att lägga lås runt alla fyra metoder, men då skalar ju koden exakt noll med fler trådar. Jag har även testat att lägga lås runt de tre subscribe / unsubscribe metoderna och sedan ha en ToList när jag itererar över sub'sen i (En kopia som då itereras över). Detta fungerar oftast men var tusende eller så iteration crashar då även ToList metoden inte är trådsäker. Dock ger detta ganska bra prestanda och skalar bra över flera trådar. Men det är ju inte hållbart att koden crashar då detta innebär att vissa klienter inte kommer få pub'en. Jag testade även att låsa kopieringen och låta resten av Handle metoden vara olåst. Detta gav knappt märkbar prestandaökning mot att låsa hela metoden.

Visa signatur
Permalänk
Medlem
Skrivet av CyberVillain:

Jag har ett litet Pub/sub Lib för SignalR, de viktigaste funktionerna finns där men innan jag pushar det till 1.0 status måste jag se till att den klarar av samtidiga operationer då flera användare kan subscriba samtidigt eller pub's itererar över sub's samtidigt som en annan tråd anropar sub.

Koden finns här
https://github.com/AndersMalmgren/SignalR.EventAggregatorProx...

Jag har valt att projicera subscriptions på två aggregatrötter, dels på event typ och dels på connectionId.

Det är 3 metoder som skriver / läser till dessa

  • Subscribe

  • Unsubscribe

  • UnsubscribeConnection

Medans en som bara läser

  • Handle

Jag skrev ett litet test för att testa att koden är trådsäker
https://github.com/AndersMalmgren/SignalR.EventAggregatorProx...

Jag har även uppdaterat den koden med lite benchmark kod så jag kan testa prestandan, men den koden har degenererat under tiden jag testat olika metoder så måste skriva om den innan jag kan commita den.

Den mest säkra metoden är att lägga lås runt alla fyra metoder, men då skalar ju koden exakt noll med fler trådar. Jag har även testat att lägga lås runt de tre subscribe / unsubscribe metoderna och sedan ha en ToList när jag itererar över sub'sen i (En kopia som då itereras över). Detta fungerar oftast men var tusende eller så iteration crashar då även ToList metoden inte är trådsäker. Dock ger detta ganska bra prestanda och skalar bra över flera trådar. Men det är ju inte hållbart att koden crashar då detta innebär att vissa klienter inte kommer få pub'en. Jag testade även att låsa kopieringen och låta resten av Handle metoden vara olåst. Detta gav knappt märkbar prestandaökning mot att låsa hela metoden.

Det måste ju bli en lösning med readers writers lock, men hur många datakällor har du? jag antar att det inte är 3 olika lock du behöver göra eftersom objektet du skriver till är densamma för alla tre?

Hur viktigt är det att databasen är 100% konsekvent? om det går att lägga till subscribers utan att dessa direkt läggs in i databasen så kan du ju ackumulera ett visst antal och sedan lägga in dessa, t.ex. genom att en tråd fixar detta var 1/10 sekund.

Permalänk
Medlem

Det kanske kan bli snabbare om du fixar 100 kopieringar medans du har lock aktivt?

Visa signatur

Dator: (Kommer senare)

Permalänk
Medlem
Skrivet av CyberVillain:

Jag har ett litet Pub/sub Lib för SignalR, de viktigaste funktionerna finns där men innan jag pushar det till 1.0 status måste jag se till att den klarar av samtidiga operationer då flera användare kan subscriba samtidigt eller pub's itererar över sub's samtidigt som en annan tråd anropar sub.

Koden finns här
https://github.com/AndersMalmgren/SignalR.EventAggregatorProx...

Jag har valt att projicera subscriptions på två aggregatrötter, dels på event typ och dels på connectionId.

Det är 3 metoder som skriver / läser till dessa

  • Subscribe

  • Unsubscribe

  • UnsubscribeConnection

Medans en som bara läser

  • Handle

Jag skrev ett litet test för att testa att koden är trådsäker
https://github.com/AndersMalmgren/SignalR.EventAggregatorProx...

Jag har även uppdaterat den koden med lite benchmark kod så jag kan testa prestandan, men den koden har degenererat under tiden jag testat olika metoder så måste skriva om den innan jag kan commita den.

Den mest säkra metoden är att lägga lås runt alla fyra metoder, men då skalar ju koden exakt noll med fler trådar. Jag har även testat att lägga lås runt de tre subscribe / unsubscribe metoderna och sedan ha en ToList när jag itererar över sub'sen i (En kopia som då itereras över). Detta fungerar oftast men var tusende eller så iteration crashar då även ToList metoden inte är trådsäker. Dock ger detta ganska bra prestanda och skalar bra över flera trådar. Men det är ju inte hållbart att koden crashar då detta innebär att vissa klienter inte kommer få pub'en. Jag testade även att låsa kopieringen och låta resten av Handle metoden vara olåst. Detta gav knappt märkbar prestandaökning mot att låsa hela metoden.

På rak arm kan jag tänka mig tre sätt att lösa detta på:

  1. Read + Write-lock

  2. Write-lock + copy

Lösning 1 och 2 använder sig av ett låsobjekt, alternativt ett per lista (det är dyrt med många små låsobjekt så testa vad som är mest effektivt för dig)

Lösning 2 är bättre om du har fler reads än writes och går ut på att du bara låser vid uppdatering av listan:

lock { var listCopy = dict[guid].Concat(new [] { item }); dict[guid] = listCopy; }

Lösning 3 är att du publicerar meddelanden till ett separat enkeltrådat kösystem som hanterar tilläggning av data i dina listor. Det finns garanterat tredjepartsbibliotek som kan göra detta åt dig.

Visa signatur

Kom-pa-TI-bilitet

Permalänk
Avstängd

@Rupertoo
Eftersom det är ett lib så kan jag inte veta exakt hur folk kommer använda det. Men normalfallet måste ju vara att det är oftare events som publiceras än det är klienter som ansluter sig till att lyssna?

Det är två * två collections alltså fyra, de ser ut såhär

private readonly IDictionary<Guid, List<Subscription>> subscriptions; private readonly IDictionary<string, List<Type>> userSubscriptions;

Den första indexerar en Dictionary på EventType (GUID då de ska funka även med Type<T, Tn> där T-Tn är godtycklig) värdet i listan är alla subscriptions som lyssnar på det eventet

Den andra indexerar på SignalR ConnectionId och listan innehåller alla events som den klienten lyssnar på

userSubscriptions används bara vid Sub / unsub medans subscriptions används från båda.

Jag har redan testat Teknocide lösning 1) och 2)
Ettan skalar ju inte alls i Multitrådat, 2an är jag lite orolig för då den kopierar enumeratorn till en ny dic. Har du många events och många som ansluter kan detta införa problem, däremot är den ju snabb vid Read då du inte behöver låsa.
3an är intressant, den är ju rätt lik tvåan men du slipper kopiering, men ska den helt ta bort trådproblem måste ju även Handle metoden köras på samma tråd och då är vi ju tillbaka vid att det inte skalar alls?

Visa signatur
Permalänk
Medlem
Skrivet av CyberVillain:

@Rupertoo
Eftersom det är ett lib så kan jag inte veta exakt hur folk kommer använda det. Men normalfallet måste ju vara att det är oftare events som publiceras än det är klienter som ansluter sig till att lyssna?

Det är två * två collections alltså fyra, de ser ut såhär

private readonly IDictionary<Guid, List<Subscription>> subscriptions; private readonly IDictionary<string, List<Type>> userSubscriptions;

Den första indexerar en Dictionary på EventType (GUID då de ska funka även med Type<T, Tn> där T-Tn är godtycklig) värdet i listan är alla subscriptions som lyssnar på det eventet

Den andra indexerar på SignalR ConnectionId och listan innehåller alla events som den klienten lyssnar på

userSubscriptions används bara vid Sub / unsub medans subscriptions används från båda.

Jag har testat Teknocide lösning 1) och 2)
Ettan skalar ju inte alls i Multitrådat, 2an är jag lite orolig för då den kopierar enumeratorn till en ny dic. Har du många events och många som ansluter kan detta införa problem, däremot är den ju snabb vid Read då du inte behöver låsa.
3an är intressant, den är ju rätt lik tvåan men du slipper kopiering, men ska den helt ta bort trådproblem måste ju även Handle metoden köras på samma tråd och då är vi ju tillbaka vid att det inte skalar alls?

Det som skalar dåligt med tvåan är C#:s list-implementation. En omuterbar länkad lista (cons-list) är väldigt attraktiv här då du kan lägga till ett element med konstant tid och inte behöver kopiera listan. Det kanske går att använda den från F# men jag har aldrig provat.

edit:
Du kan göra så här:

private static readonly FSharpList<Subscription> Nil = FSharpList<Subscription>.Empty; private FSharpList<Subscription> Subscriptions = Nil; private readonly object myLock = new object(); public void AddOne(Subscription s) { lock (myLock) { this.Subscriptions = FSharpList<Subscription>.Cons(s, this.Subscriptions); } } public IEnumerable<Subscription> GetSubscriptionsLockFree() { return this.Subscriptions; }

För att slippa synka nycklarna på userSubscriptions bör du använda en ConcurrentDictionary

Visa signatur

Kom-pa-TI-bilitet

Permalänk
Avstängd
Skrivet av Teknocide:

Det som skalar dåligt med tvåan är C#:s list-implementation. En omuterbar länkad lista (cons-list) är väldigt attraktiv här då du kan lägga till ett element med konstant tid och inte behöver kopiera listan. Det kanske går att använda den från F# men jag har aldrig provat.

lustigt, tänkte precis på länkade listor, ska testa #2 med det. Tack för input

edit: dock kan den inte vara omuterbar då jag måste kunna ta bort subscriptions

Visa signatur
Permalänk
Medlem
Skrivet av CyberVillain:

lustigt, tänkte precis på länkade listor, ska testa #2 med det. Tack för input

edit: dock kan den inte vara omuterbar då jag måste kunna ta bort subscriptions

List-typen kan gott vara omutbar, men referensen behöver inte vara det Se edit ovan.

Visa signatur

Kom-pa-TI-bilitet

Permalänk
Avstängd

Men jag måste ju ta bort gamla subscriptions annars får du en fet lista till slut som tar O(N) där vädligt liten del av N faktiskt är aktiva subscritions att läsa igenom för att inte tala om minnesluckan det skapar, men du kanske menar nått annat?

Visa signatur
Permalänk
Medlem
Skrivet av CyberVillain:

Men jag måste ju ta bort gamla subscriptions annars får du en fet lista till slut som tar O(N) där vädligt liten del av N faktiskt är aktiva subscritions att läsa igenom för att inte tala om minnesluckan det skapar, men du kanske menar nått annat?

Du skapar en ny lista helt enkelt där du lägger till de som ska finnas kvar

var newList = Nil; foreach (sub in subscriptions[guid].Where(s => s.ConnectionId != connectionId)) { newList = FSharpList<Subscription>.Cons(sub, newList); }

Visa signatur

Kom-pa-TI-bilitet

Permalänk
Avstängd

Får benchmarka lite men jag undrar om det blir så mycket snabbare än en list då jag måste ta bort subscriptions.
Dock tror jag inte jag vill ha beroenden till FSharp.Core.dll då detta är ett lib som man kan installera via Nuget, tror folk lackar lite om mitt paket drar in F#

Men borde finns Imutable linked lists på nätet för C#, eller skriva en själv, inte så svårt då jag bara behöver vissa metoder.

Det lutar iallafall åt Locked write unlocked read eller vad säger ni?

edit: Mitt lilla enhetstest klarade att subba / unsubba samtidigt som 1 miljon events triggade på 11 sekunder och då med min list implementation så kanske inte behöver oroa mig för Listans prestanda.
Dock så kör det testet unsub lika ofta som sub mao inte så många subs samtidigt. För att verkligen testa Listan impakt på prestandna borde jag köra unsub mycket mer sällan i testet så det ligger ett par tusen sub's i listan

Visa signatur
Permalänk
Medlem
Skrivet av CyberVillain:

Får benchmarka lite men jag undrar om det blir så mycket snabbare än en list då jag måste ta bort subscriptions.
Dock tror jag inte jag vill ha beroenden till FSharp.Core.dll då detta är ett lib som man kan installera via Nuget, tror folk lackar lite om mitt paket drar in F#

Men borde finns Imutable linked lists på nätet för C#, eller skriva en själv, inte så svårt då jag bara behöver vissa metoder.

Det lutar iallafall åt Locked write unlocked read eller vad säger ni?

edit: Mitt lilla enhetstest klarade att subba / unsubba samtidigt som 1 miljon events triggade på 11 sekunder och då med min list implementation så kanske inte behöver oroa mig för Listans prestanda.
Dock så kör det testet unsub lika ofta som sub mao inte så många subs samtidigt. För att verkligen testa Listan impakt på prestandna borde jag köra ubsub mycket mer sällan i testet så det ligger ett par tusen sub's i listan

Med en bra implementation borde inte den länkade listan vara en flaskhals. Även en vanlig lista har dålig prestanda när man tar bort i mitten eftersom varje efterföljande element måste flyttas ett snäpp åt vänster.

Visa signatur

Kom-pa-TI-bilitet

Permalänk
Avstängd

Men både List<T>(ICollection)
och List.RemoveAt(int index)

Gör Memcopy under ytan, så visst den måste flytta alla bytes i minnet, men kan det verkligen ta mer tid än O(N) för borttagning av ett element?

edit: Jag gör ju iof inte RomveAt utan Remove vilket i värsta fall ger O(N) men i snitt O(N/2)

Visa signatur
Permalänk
Medlem
Skrivet av CyberVillain:

Men både List<T>(ICollection)
och List.RemoveAt(int index)

Gör Memcopy under ytan, så visst den måste flytta alla bytes i minnet, men kan det verkligen ta mer tid än O(N) för borttagning av ett element?

edit: Jag gör ju iof inte RomveAt utan Remove vilket i värsta fall ger O(N) men i snitt O(N/2)

RemoveAll gör antagligen precis samma sak som koden jag skrev för den länkade listan, dvs skapar en ny lista och lägger i de objekt som inte matchar prediktatet. En naivare implementation som gjorde Remove() om och om igen skulle ge O(N^2). Även RemoveAt är O(N) på grund av minneskopieringen som görs (som är O(N))

Visa signatur

Kom-pa-TI-bilitet

Permalänk
Avstängd
Skrivet av Teknocide:

RemoveAll gör antagligen precis samma sak som koden jag skrev för den länkade listan, dvs skapar en ny lista och lägger i de objekt som inte matchar prediktatet. En naivare implementation som gjorde Remove() om och om igen skulle ge O(N^2). Även RemoveAt är O(N) på grund av minneskopieringen som görs (som är O(N))

Oj, blev galet där, fick för mig jag använde Remove, men det går ju inte
Memcopy shiftar bitarna så går inte att jämföra med O(N) i managed code. Men iaf, får testa lite och benchmarka.

edit: Däremot måste den ju göra Memcopy O(N2) där N = antalet borttagna element

Visa signatur
Permalänk
Medlem
Skrivet av CyberVillain:

Oj, blev galet där, fick för mig jag använde Remove, men det går ju inte
Memcopy shiftar bitarna så går inte att jämföra med O(N) i managed code. Men iaf, får testa lite och benchmarka.

edit: Däremot måste den ju göra Memcopy O(N2) där N = antalet borttagna element

Det är sant att O(N) inte är jämförbart med O(N) men Array.Copy är en O(N)-funktion
http://msdn.microsoft.com/en-us/library/z50k9bft.aspx

Visa signatur

Kom-pa-TI-bilitet

Permalänk
Avstängd

Fast det är en copy av pekera den gör till en ny chunk av minne. aja, skit samma ska testa Immutable linked list iaf. Mest för att jag vill bencha den mot en List

Och då menar jag bencha den i mitt scenario

Visa signatur
Permalänk
Medlem
Skrivet av CyberVillain:

Fast det är en copy av pekera den gör till en ny chunk av minne. aja, skit samma ska testa Immutable linked list iaf. Mest för att jag vill bencha den mot en List

Och då menar jag bencha den i mitt scenario

Kul, berätta gärna vad det blir för resultat

Visa signatur

Kom-pa-TI-bilitet

Permalänk
Medlem
Skrivet av CyberVillain:

Fast det är en copy av pekera den gör till en ny chunk av minne. aja, skit samma ska testa Immutable linked list iaf. Mest för att jag vill bencha den mot en List

Och då menar jag bencha den i mitt scenario

Alltså om jag har fattat det rätt så skall du ha subscribers som kan lägga till sig och ta bort sig, allt du då behöver göra är att ha en kö för vardera metod som de lägger till sig i. När du sedan skall skicka ett event så låser du båda dessa köerna och kör din hushållning med vilka som ska vara kvar i dom privata listorna som bara är tillgänglig från eventtråden och två köer som har ett varsit lock. Det är ju ändå ingen ide att lägga till eller ta bort någon subscription innan du ska skicka ett event. Direkt du har gjort hushållningen kan du släppa locken på köerna och hantera dom interna datastrukturerna.

Permalänk
Datavetare

Rupertoo hade en väldigt relevant fråga i sitt första inlägg: måste Subscribe, Unsubscribe och UnsubscribeConnection ta effekt direkt när de anropas eller är det OK med "eventual consistency".

Det går att få detta att skala i princip linjärt till rätt många CPU-kärnor (förutsatt att VM-en inte ställer till det) om "eventual consistency" är OK.

Visa signatur

Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer

Permalänk
Medlem
Skrivet av Rupertoo:

Det måste ju bli en lösning med readers writers lock, men hur många datakällor har du? jag antar att det inte är 3 olika lock du behöver göra eftersom objektet du skriver till är densamma för alla tre?

Hur viktigt är det att databasen är 100% konsekvent? om det går att lägga till subscribers utan att dessa direkt läggs in i databasen så kan du ju ackumulera ett visst antal och sedan lägga in dessa, t.ex. genom att en tråd fixar detta var 1/10 sekund.

Skrivet av virtual void:

Rupertoo hade en väldigt relevant fråga i sitt första inlägg: måste Subscribe, Unsubscribe och UnsubscribeConnection ta effekt direkt när de anropas eller är det OK med "eventual consistency".

Det går att få detta att skala i princip linjärt till rätt många CPU-kärnor (förutsatt att VM-en inte ställer till det) om "eventual consistency" är OK.

Blir man inte i sådana fall tvungen att synkronisera "journalen" mellan trådar? Om två klienter registerar sig samtidigt måste man låsa så att bägge inte försöker köra this.eventJournal.Add(event) samtidigt. Eller menar ni att man på något sätt bör köra en journal per tråd och vid behov sammanställa dessa?

Visa signatur

Kom-pa-TI-bilitet

Permalänk
Medlem
Skrivet av Teknocide:

Blir man inte i sådana fall tvungen att synkronisera "journalen" mellan trådar? Om två klienter registerar sig samtidigt måste man låsa så att bägge inte försöker köra this.eventJournal.Add(event) samtidigt. Eller menar ni att man på något sätt bör köra en journal per tråd och vid behov sammanställa dessa?

Om all journalföring uppdateras i handle(eventskaparen) så behöver man inte bry sig om att låsa de två journalköerna tillsammans i subscribe och unsubscribe utan detta sköts i handle.

Det blir ju på det här sättet eftersom det är helt meningslöst att göra någon journalföring alls innan ett event skall skickas.

när handle exekveras så kollar den först om det finns något i subscribe kön, låser denna om det finns och inför subscribe i den klassens privata journal.
Släpper låset på denna och kollar ifall det finns några inlägg i unsubscribe kön, går igenom dessa och tar bort dessa från den privata journalen.
Släpper låset på denna och utför eventhanteringen.

Eventuellt behöver du inte ha några lås alls på köerna eftersom man bara plockar sista inkomna tills kön är tom.

Alltså klassen skall ha följande.
subscribe kö
unsubscribe kö
journal datastruktur som bara handle arbetar på.

Permalänk
Medlem
Skrivet av Rupertoo:

Om all journalföring uppdateras i handle(eventskaparen) så behöver man inte bry sig om att låsa de två journalköerna tillsammans i subscribe och unsubscribe utan detta sköts i handle.

när handle exekveras så kollar den först om det finns något i subscribe kön, låser denna om det finns och inför subscribe i den klassens privata journal.
Släpper låset på denna och kollar ifall det finns några inlägg i unsubscribe kön, går igenom dessa och tar bort dessa från den privata journalen.
Släpper låset på denna och utför eventhanteringen.

Eventuellt behöver du inte ha några lås alls på köerna eftersom man bara plockar sista inkomna tills kön är tom.

Min fundering är hur man kan lägga till ett ärende i en kö utan att låsa på samlingen: Det kan hända att två klienter vill lägga till varsitt ärende på samma gång, och då behövs någon sorts synkronisering av kön för att säkerställa att de båda Add-operationerna inte krockar.
Tänker jag fel? Ge gärna ett kodexempel.

Visa signatur

Kom-pa-TI-bilitet

Permalänk
Medlem
Skrivet av Teknocide:

Min fundering är hur man kan lägga till ett ärende i en kö utan att låsa på samlingen: Det kan hända att två klienter vill lägga till varsitt ärende på samma gång, och då behövs någon sorts synkronisering av kön för att säkerställa att de båda Add-operationerna inte krockar.
Tänker jag fel? Ge gärna ett kodexempel.

Alltså metoden subscribe inför data i sin egen kö som sedan används av handle.
metoden unsubscribe inför data i sin egen kö som sedan används av handle.
när handle ska skicka ett event låser den köerna i tur och ordning o genomför journalhållningen, journalen används bara av handle.

Man behöver eventuellt låsa varje kö för sig. Så att bara en tråd är inne i metoden.

Vinsten är att det inte kan bli någon deadlock eftersom handle bara låser en kö i taget. Du kan alltså göra en subscribe och en unsubscribe samtidigt.

Handle behöver inte låsa köerna alls eftersom den bara tömmer dem.(antingen är ju kön tom eller så finns det ännu ett element att ta ut, detta påverkar ju inte insättningen.)

Permalänk
Datavetare
Skrivet av Teknocide:

Blir man inte i sådana fall tvungen att synkronisera "journalen" mellan trådar? Om två klienter registerar sig samtidigt måste man låsa så att bägge inte försöker köra this.eventJournal.Add(event) samtidigt. Eller menar ni att man på något sätt bör köra en journal per tråd och vid behov sammanställa dessa?

Hade inte läst källkoden innan jag skrev min kommentar, bara läst tråden, så en revidering: det kommer att skala med eventual consistency" men det kommer bara nära linjärt när man har väldigt många subscribers så Subscribe, Unsubscribe, UnsubscribeConnection och Handle är relativt dyra. Problemet är att man kommer behöva en räknare som måste delas mellan alla trådar då man måste veta i vilken ordning Subscribe, Unsubscribe & UnsubscribeConnection anropades i... Hade ordningen man anropar dessa inte spelat någon roll hade man sluppit detta.

Sedan kan det faktiskt vara så att det inte skalar alls just nu för vem vet hur väl de anrop som faktiskt görs i koden skalar, t.ex. skapa en ny GUID från flera trådar.

Men i stort kan man ha M*N länkade listor (där N endera är ett fix tal, ett par gånger större än antalet trådar alt. så är N exakt lika med antalet trådar men har aldrig använt ThreadLocal<T> i C# så vet inte om den saknar lås eller ej). M är i detta fall 3 då vi har 3 olika metoder.

Används ThreadLocal<T> så lägger man bara in ett entry i listan som innehåller argumenten till funktionen + det globala sekvensnumret. Det lås som behövs här kommer nästa enbart tas av samma tråd == väldigt billigt då den cache-line som låset ligger på kommer alltid vara i tillstånd modified av den lokala CPU-kärnan så operationen är helt lokal.

I fallet där man har fix antal hinkar har man ett lås per hink + att varje hink måste ligga på en egen cache-line (hur fixar man det i C#???). Tråd ID använd sedan som ett hash-värde som avgör vilken hink man lägger samma typ av entry som ovan.

Med frekvens F (som ska vara relativt låg, max 100 gånger i sekunden, helst mindre) så går man igenom alla listor, utför operationerna i ordning och sparar resultatet i datastrukturer som initierats med strukturer som bara läses av Handle. När man är klar använder man Interlocked.CompareAndExchange för att göra de nya strukturerna synliga för Handle. I.e. Handle kan alltid läsa helt utan lås.

Read/write lås fungerar mycket mer sällan än man tycker att det borde. Anledningen är att detta typ av lås typiskt kräver att även läsare gör en skrivning till minst en cache-line (på något sätt måste man markera att en läsare är inom den skyddare regionen). Om detta görs från flera trådar blir den operationen väldigt dyr då cache-line:en kommer vara i tillstånd modified på "fel" CPU-kärnan, vilket betyder att den kärna som äger cache-line:en måste skriva ner resultatet (så den går till tillstånd exklusive), den nya tråden läser resultatet (så cache-line:en går till shared) för att sedan skriva att den kliver in (måste nu invalideras på den andra kärnan och sättas till modified på denna). Allt detta kan lätt ta många 100-tals cykler!

Vet man exakt hur många trådar man har och det antalet aldrig ändras kan man göra ett väldigt skalbara read/write-lås genom att ha en mutex per tråd. Läsare låser bara "sin" mutex som då blir billigt då det är en lokal operation. Skrivare låser alla lås, så man garanterar att ingen annan är inom regionen vid skrivning (som då måste vara mycket mer sällsynt än läsning). I detta fall blir läsare från olika trådar helt oberoende av varandra så det skalar i princip linjärt.

Visa signatur

Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer

Permalänk
Medlem
Skrivet av Rupertoo:

Alltså metoden subscribe inför data i sin egen kö som sedan används av handle.
metoden unsubscribe inför data i sin egen kö som sedan används av handle.
när handle ska skicka ett event låser den köerna i tur och ordning o genomför journalhållningen, journalen används bara av handle.

Man behöver eventuellt låsa varje kö för sig. Så att bara en tråd är inne i metoden.

Vinsten är att det inte kan bli någon deadlock eftersom handle bara låser en kö i taget. Du kan alltså göra en subscribe och en unsubscribe samtidigt.

Jag förstod hur du tänkte med journalerna men missförstod hur du menade att det skalade. Det fetmarkerade i citeringen visar informationen jag saknade.

ConcurrentQueue borde vara perfekt för den lösningen. Det kommer trots detta behövas ett lås när man ska sammanställa den effektiva listan av subscribers, dvs vid varje Handle(), om jag inte misstar mig.

Förresten, blir detta upplägg en typ av STM?

Visa signatur

Kom-pa-TI-bilitet

Permalänk
Medlem
Skrivet av Teknocide:

Jag förstod hur du tänkte med journalerna men missförstod hur du menade att det skalade. Det fetmarkerade i citeringen visar informationen jag saknade.

ConcurrentQueue borde vara perfekt för den lösningen. Det kommer trots detta behövas ett lås när man ska sammanställa den effektiva listan av subscribers, dvs vid varje Handle(), om jag inte misstar mig.

Förresten, blir detta upplägg en typ av STM?

Jo du har rätt att man måste låsa subscribekön och unsubscibekön även i handle, jag insåg att man kunde ju vara inne i handle och samtidigt som man gjort klart subscribekön, har en annan tråd hunnit inserta en subscribe och en unsubscribe, och då unsubscribe man en icke existerande för att sedan subscribe en som borde vara borttagen. Men ok denna ordning funkar 100%

i handle.
om kön inte är tom, lås subscribekö, gå igenom denna. låsupp subscribekö
om kön inte är tom, lås unsubscribekö, gå igenom denna. låsupp unsubscribe
skicka event på vanligt sätt.

Utveckla STM acronymen

Permalänk
Medlem
Skrivet av Rupertoo:

Jo du har rätt att man måste låsa subscribekön och unsubscibekön även i handle, jag insåg att man kunde ju vara inne i handle och samtidigt som man gjort klart subscribekön, har en annan tråd hunnit inserta en subscribe och en unsubscribe, och då unsubscribe man en icke existerande för att sedan subscribe en som borde vara borttagen. Men ok denna ordning funkar 100%

i handle.
om kön inte är tom, lås subscribekö, gå igenom denna. låsupp subscribekö
om kön inte är tom, lås unsubscribekö, gå igenom denna. låsupp unsubscribe
skicka event på vanligt sätt.

Utveckla STM acronymen

STM == Software Transactional Memory, men efter att ha läst artikeln i länken inser jag att det bara är journaldelen som är liknande.

Visa signatur

Kom-pa-TI-bilitet

Permalänk
Datavetare
Skrivet av Teknocide:

STM == Software Transactional Memory, men efter att ha läst artikeln i länken inser jag att det bara är journaldelen som är liknande.

STM är väldigt trevligt att jobba med, men ingen har lyckats göra en implementation som är speciellt snabb. HTM (Hardware Transaction Memory) har däremot börjat dyka upp i ett par CPUer, finns bl.a. i senaste IBM POWER och finns även i de Haswell CPUer som har TSX. Har pratat med ingenjörer på Intel om TSX och de sa ganska uppriktigt att TSX inte är den slutgiltiga lösningen, men upp till 4 CPUer fungerar det riktigt bra!

Har lekt en del med ConcurrentQueue i .Net och det verkar inte riktigt som att den är tänkt för användarfallet att man har consumers/producers som lägger in/tar ut data med väldigt hög frekvens. Såg kommenterar att det mer är tänkt som en "säker kö som du vill använda från flera trådar", i.e. poängen är funktionen att man kan använda den från flera trådar, inte att det skulle vara supereffektivt. Gjorde en egen ganska naiv kö-implementation baserad på Interlock.CompareAndExchange() som hade väldigt snarligt beteende/skalning (verkar vara en pekare per ända man kör CompareAndExchange på).

Man kan göra en mer skalbar ConcurrentQueue genom att ha N köer. Initialt har man en "vänster-pekare" med index 1 och en högerpekare med index 0. Index väljer ut en av de N köerna, som var och en endera använder någon lock-less insert/remove eller helt enkelt ett lås per hink.
Lägger man till till vänster gör man en fetch_and_add på index (som då blir 2 och man får tillbaka 1), detta ger 1 mod N, d.v.s hink 1. Nästa gång man lägger till får man en 2:a, så det hamnar i hink 2 mod N.

Lägger man i stället till åt höger gör man i stället fetch_and_sub, så nästa index blir -2 och hinken blir -1 mod N, etc. Plocka ut element gör man genom att köra index åt motsatt håll.

En sådan kö kommer i princip aldrig blocka av att man köar i ena änden och plockar av i andra (vanligaste use-case:et), givet ett tillräckligt stort värde på N.

Visa signatur

Care About Your Craft: Why spend your life developing software unless you care about doing it well? - The Pragmatic Programmer

Permalänk
Avstängd

Grabbar jag är lite för full för att läsa era inlägg nu, burger night med grabbarna, lovar att läsa och återkomma imorn! Skitkul att ni har input!

Bonusbild

Visa signatur