Ajánlott eljárások felügyelt szálkezeléshez
A többszálúság gondos programozást igényel. A legtöbb feladat esetében csökkentheti a bonyolultságot, ha sorba rendezi a végrehajtási kérelmeket a szálkészlet szálai alapján. Ez a témakör a nehezebb helyzetekkel foglalkozik, például több szál munkájának koordinálásával vagy a letiltott szálak kezelésével.
Feljegyzés
A 4. .NET-keretrendszer kezdve a feladat párhuzamos kódtára és a PLINQ olyan API-kat biztosít, amelyek csökkentik a többszálas programozás összetettségének és kockázatainak egy részét. További információ: Párhuzamos programozás a .NET-ben.
Holtpontok és versenyfeltételek
A többszálas megoldás az átviteli sebességgel és a válaszképességgel kapcsolatos problémákat oldja meg, de ezzel új problémákat vezet be: holtpontok és versenyhelyzetek.
Holtpontok
Holtpont akkor fordul elő, ha két szál mindegyike megpróbál zárolni egy erőforrást, amelyet a másik már zárolt. Egyik szál sem tud további haladást elérni.
A felügyelt szálkezelés osztályainak számos módszere időtúllépést biztosít a holtpontok észleléséhez. Az alábbi kód például megpróbál zárolást szerezni egy nevű lockObject
objektumon. Ha a zárolást nem 300 ezredmásodpercben kapják meg, Monitor.TryEnter akkor a visszaadott false
érték.
If Monitor.TryEnter(lockObject, 300) Then
Try
' Place code protected by the Monitor here.
Finally
Monitor.Exit(lockObject)
End Try
Else
' Code to execute if the attempt times out.
End If
if (Monitor.TryEnter(lockObject, 300)) {
try {
// Place code protected by the Monitor here.
}
finally {
Monitor.Exit(lockObject);
}
}
else {
// Code to execute if the attempt times out.
}
Versenyfeltételek
A versenyfeltétel egy olyan hiba, amely akkor fordul elő, ha egy program eredménye attól függ, hogy két vagy több szál közül melyik éri el először egy adott kódblokkot. A program többszöri futtatása különböző eredményeket eredményez, és egy adott futtatás eredménye nem jelezhető előre.
A versenyfeltételek egyszerű példája a mező növekménye. Tegyük fel, hogy egy osztály rendelkezik egy privát statikus mezővel (a Visual Basicben megosztva ), amely az osztály egy példányának létrehozásakor növekszik, például objCt++;
a (C#) vagy objCt += 1
a (Visual Basic) kód használatával. Ehhez a művelethez be kell tölteni az értéket egy regiszterbeobjCt
, növelni kell az értéket, és el kell őket tárolni.objCt
Többszálas alkalmazásokban előfordulhat, hogy egy olyan szál, amely betöltötte és megnövelte az értéket, egy másik szál előtagja lehet, amely mindhárom lépést végrehajtja; amikor az első szál folytatja a végrehajtást, és tárolja az értékét, felülírja objCt
, anélkül, hogy figyelembe veszi, hogy az érték időközben megváltozott.
Ez a faji feltétel könnyen elkerülhető az osztály módszereivel Interlocked , például Interlocked.Increment. A többszálas adatszinkronizálás egyéb technikáiról a Többszálas adatok szinkronizálása című témakörben olvashat.
A versenyfeltételek akkor is előfordulhatnak, ha több szál tevékenységeit szinkronizálja. Amikor kódsort ír, figyelembe kell vennie, hogy mi történhet, ha a sor végrehajtása előtt elő lett készítve egy szál (vagy a sort alkotó egyes gépi utasítások bármelyike), és egy másik szál felülírta azt.
Statikus tagok és statikus konstruktorok
Az osztály nem inicializálódik, amíg az osztálykonstruktor (static
c#-ban konstruktor a Shared Sub New
Visual Basicben) nem fut. Ha meg szeretné akadályozni a kód nem inicializált típuson történő végrehajtását, a közös nyelvi futtatókörnyezet letiltja az osztálytagok static
(Shared
Visual Basic-tagok) más szálakból érkező összes hívást, amíg az osztálykonstruktor le nem fut.
Ha például egy osztálykonstruktor új szálat indít el, és a menet eljárása meghívja az osztály egy static
tagját, az új szál addig blokkol, amíg az osztálykonstruktor befejeződik.
Ez minden olyan típusra vonatkozik, amely konstruktort static
tartalmazhat.
Processzorok száma
A többszálú architektúrára hatással lehet, hogy több processzor vagy csak egy processzor áll rendelkezésre egy rendszeren. További információ: Processzorok száma.
Environment.ProcessorCount A tulajdonság használatával meghatározhatja a futtatáskor elérhető processzorok számát.
Általános javaslatok
Több szál használatakor vegye figyelembe az alábbi irányelveket:
Ne használjon Thread.Abort más szálak megszakítására. Egy másik szál meghívása
Abort
hasonló ahhoz, hogy kivételt dobjon az adott szálra anélkül, hogy tudná, hogy a szál milyen pontot ért el a feldolgozás során.Ne használja Thread.Suspend és Thread.Resume szinkronizálja több szál tevékenységeit. Használja Mutexa , ManualResetEvent, AutoResetEventés Monitor.
Ne szabályozza a munkaszálak végrehajtását a fő programból (például események használatával). Ehelyett úgy tervezze meg a programot, hogy a munkaszálak várhassák, amíg a munka elérhetővé válik, végrehajtsa, és ha végzett, értesítse a program más részeit. Ha a munkavégző szálak nem blokkolnak, fontolja meg a szálkészlet-szálak használatát. Monitor.PulseAll olyan helyzetekben hasznos, amikor a munkaszálak blokkolva vannak.
Ne használjon típusokat zárolási objektumként. Ez azt jelzi, hogy kerülje az olyan kódokat, mint
lock(typeof(X))
a C# vagySyncLock(GetType(X))
a Visual Basic, vagy az objektumok használata Monitor.EnterType . Egy adott típus esetében alkalmazástartományonként csak egy példány System.Type található. Ha a zárolás típusa nyilvános, a sajáttól eltérő kód zárolhatja azt, ami holtpontokhoz vezethet. További problémákért tekintse meg a megbízhatóság ajánlott eljárásait.Körültekintően zároljon példányokat, például
lock(this)
C# vagySyncLock(Me)
Visual Basic nyelven. Ha az alkalmazás más, típuson kívüli kódja zárolja az objektumot, holtpontok léphetnek fel.Győződjön meg arról, hogy a figyelőbe belépett szálak mindig elhagyják ezt a figyelőt, még akkor is, ha kivétel történik, miközben a szál a figyelőben van. A C#-zárolási utasítás és a Visual Basic SyncLock utasítás automatikusan biztosítja ezt a viselkedést, és végül egy blokkot alkalmazva biztosítja a Monitor.Exit meghívást. Ha nem tudja biztosítani a kilépés meghívását, fontolja meg a terv módosítását a Mutex használatára. A rendszer automatikusan felszabadít egy mutexet, amikor a jelenleg tulajdonában lévő szál leáll.
Használjon több szálat a különböző erőforrásokat igénylő tevékenységekhez, és ne rendeljen több szálat egyetlen erőforráshoz. Az I/O-t érintő bármely feladatnak például előnye, hogy saját szála van, mivel ez a szál blokkolva lesz az I/O-műveletek során, és így lehetővé teszi más szálak végrehajtását. A felhasználói bemenet egy másik erőforrás, amely egy dedikált szál előnyeit élvezi. Az egyprocesszoros számítógépeken a felhasználói bemenettel rendelkező és az I/O-t használó, de több számítási igényű tevékenység is egymással verseng.
Fontolja meg az osztály metódusainak használatát az Interlocked egyszerű állapotváltozásokhoz az utasítás használata helyett (
SyncLock
alock
Visual Basicben). Azlock
állítás jó általános célú eszköz, de az Interlocked osztály jobb teljesítményt nyújt az atomi frissítésekhez. Belsőleg egyetlen zárolási előtagot hajt végre, ha nincs versengés. A kódismétlésekben figyelje meg az alábbi példákban láthatóhoz hasonló kódokat. Az első példában egy állapotváltozó növekszik:SyncLock lockObject myField += 1 End SyncLock
lock(lockObject) { myField++; }
Az utasítás helyett a metódus használatával javíthatja a Increment
lock
teljesítményt az alábbiak szerint:System.Threading.Interlocked.Increment(myField)
System.Threading.Interlocked.Increment(myField);
Feljegyzés
Használja a metódust az Add 1-nél nagyobb atomi növekményekhez.
A második példában a referenciatípus változó csak akkor frissül, ha null értékű hivatkozás (
Nothing
a Visual Basicben).If x Is Nothing Then SyncLock lockObject If x Is Nothing Then x = y End If End SyncLock End If
if (x == null) { lock (lockObject) { x ??= y; } }
A teljesítmény a módszer használatával CompareExchange javítható, az alábbiak szerint:
System.Threading.Interlocked.CompareExchange(x, y, Nothing)
System.Threading.Interlocked.CompareExchange(ref x, y, null);
Feljegyzés
A CompareExchange<T>(T, T, T) metódus túlterhelése típusbiztos alternatívát kínál a referenciatípusokhoz.
Javaslatok osztálykódtárakhoz
Többszálú osztálykódtárak tervezésekor vegye figyelembe az alábbi irányelveket:
Ha lehetséges, kerülje a szinkronizálás szükségességét. Ez különösen igaz az erősen használt kódokra. Előfordulhat például, hogy egy algoritmus úgy van beállítva, hogy elviseljen egy versenyhelyzetet ahelyett, hogy megszüntetné azt. A szükségtelen szinkronizálás csökkenti a teljesítményt, és holtpontok és versenyfeltételek lehetőségét teremti meg.
A statikus adatok (
Shared
a Visual Basicben) szál biztonságossá tétele alapértelmezés szerint.Alapértelmezés szerint ne tegye biztonságossá a példány adatszálát. A szálbiztos kód létrehozásához zárolt elemek hozzáadása csökkenti a teljesítményt, növeli a zárolási versengést, és lehetővé teszi a holtpontok előfordulását. A gyakori alkalmazásmodellekben egyszerre csak egy szál hajtja végre a felhasználói kódot, ami minimálisra csökkenti a szálbiztonság szükségességét. Ezért a .NET-osztálykódtárak alapértelmezés szerint nem biztonságosak.
Kerülje a statikus állapotot módosító statikus metódusok megadását. A gyakori kiszolgálói forgatókönyvekben a statikus állapot meg van osztva a kérelmek között, ami azt jelenti, hogy egyszerre több szál is végrehajthatja ezt a kódot. Ez megnyitja a szálak hibáinak lehetőségét. Érdemes lehet olyan tervezési mintát használni, amely adatokat ágyaz be a kérések között nem megosztott példányokba. Továbbá ha a statikus adatok szinkronizálva vannak, az állapotot módosító statikus metódusok közötti hívások holtpontot vagy redundáns szinkronizálást eredményezhetnek, ami hátrányosan befolyásolhatja a teljesítményt.