.NET
Kif Nifhem il-Concurrency f'.NET
Ħsibt li concurrency kienet sempliċiment tfisser threads. Tkopri race conditions, deadlocks, transazzjonijiet, u l-problema tal-lost update, u lock ta' C# ma jagħmel xejn biex jipproteġi row ta' database.
Meta l-ewwel smajt il-kelma concurrency, ħsibt li sempliċiment kienet tfisser l-użu ta' diversi threads.
Wara li studjajt aktar dwar .NET u SQL Server, tgħallimt li concurrency hija suġġett usa'. Isseħħ kull darba li diversi operazzjonijiet ikunu għaddejjin u jistgħu jaċċessaw jew jimmodifikaw l-istess riżorsi. Żewġ threads jibdlu l-istess varjabbli. Żewġ talbiet API jaġġornaw l-istess rekord tad-database. Żewġ transazzjonijiet jistennew riżorsi miżmuma minn xulxin. Żewġ utenti jeditjaw l-istess informazzjoni kważi fl-istess ħin.
Bħala junior developer m'għandix bżonn naf kull dettall intern tat-threads, il-locks, u l-magna ta' SQL Server. Għandi nifhem l-problemi l-aktar komuni u l-għodod disponibbli biex nittrattahom.
X'tfisser verament il-concurrency
Concurrency tfisser li diversi operazzjonijiet qed jagħmlu progress matul l-istess perjodu.
Immaġina API ta' ASP.NET Core tirċievi żewġ talbiet f'kważi l-istess ħin:
Request A: Buy the last available product
Request B: Buy the last available product
Iż-żewġ talbiet jistgħu jaqraw li l-prodott għandu unità waħda disponibbli. Jekk it-tnejn ikomplu mingħajr ebda protezzjoni, it-tnejn jistgħu jlestu x-xiri, u s-sistema tbiegħ l-istess oġġett darbtejn.
Dak għaliex dan jimporta. Kodiċi li jaħdem tajjeb b'utent wieħed jista' jipproduċi riżultati mhux mistennija meta diversi utenti jilqtuh fl-istess ħin.
Concurrency tidher fi tliet livelli: ġewwa l-applikazzjoni .NET meta threads jew tasks jaqsmu dejta, ġewwa SQL Server meta transazzjonijiet jaċċessaw l-istess rows jew tabelli, u bejn istanzi tal-applikazzjoni meta l-app taħdem fuq diversi servers jew containers.
Dawn il-livelli huma konnessi, iżda mhumiex ikkontrollati bl-istess mod. lock ta' C# jista' jipproteġi memorja ġewwa istanza waħda tal-applikazzjoni. Ma jistax jipproteġi row ta' SQL Server minn application server ieħor.
Race conditions
Race condition isseħħ meta r-riżultat ta' operazzjoni jiddependi fuq liema thread jilħaq biċċa kodiċi l-ewwel.
Ikkunsidra kaunter sempliċi:
public class RequestCounter
{
private int _count;
public void Increment()
{
_count++;
}
}
Dak _count++ jidher bħal operazzjoni waħda, iżda internament huwa tliet passi: aqra l-valur attwali, żid wieħed, aħżen il-valur il-ġdid.
Ejja ngħidu li l-valur attwali huwa 10. Żewġ threads jistgħu t-tnejn jaqraw 10, it-tnejn jikkalkulaw 11, u t-tnejn jaħżnu 11. Ir-riżultat mistenni kien 12. Ir-riżultat attwali huwa 11.
Għal operazzjonijiet numeriċi sempliċi, .NET jipprovdi l-klassi Interlocked:
using System.Threading;
public class RequestCounter
{
private int _count;
public void Increment()
{
Interlocked.Increment(ref _count);
}
}
Interlocked.Increment iwettaq iż-żieda bħala operazzjoni atomika waħda, għalhekk thread ieħor ma jistax jindaħal f'nofsha.
Nużaw lock f'C#
Għal operazzjonijiet aktar kumplessi, C# jipprovdi l-istqarrija lock:
public class BankAccount
{
private readonly object _balanceLock = new();
private decimal _balance;
public void Deposit(decimal amount)
{
lock (_balanceLock)
{
_balance += amount;
}
}
}
Thread wieħed biss jista' jesegwixxi s-sezzjoni protetta bl-użu ta' dak l-oġġett lock f'daqqa waħda.
Iżda li żżid locks kullimkien mhix soluzzjoni tajba. Locks inaqqsu l-prestazzjoni, iġagħlu threads jistennew, iżidu l-komplessità, jintroduċu deadlocks, u jistgħu jaħbu problemi kkawżati minn disinn fqir. Fejn possibbli huwa aħjar li tevita stat mutabbli maqsum milli tipproteġi kollox b'locks.
lock ta' C# jaħdem ukoll biss ġewwa l-proċess li jippossjedih. Jekk applikazzjoni taħdem fuq tliet servers, kull server għandu l-oġġett lock tiegħu stess. Il-concurrency tad-database trid tiġi ttrattata mid-database jew permezz ta' mekkaniżmu distribwit ieħor.
Deadlocks
Deadlock isseħħ meta żewġ operazzjonijiet jistennew permanentement riżorsi miżmuma minn xulxin.
Thread A:
1. Locks Resource 1
2. Waits for Resource 2
Thread B:
1. Locks Resource 2
2. Waits for Resource 1
L-ebda thread ma jista' jkompli. L-istess ħaġa sseħħ f'SQL Server:
Transaction A:
1. Updates Customer 1
2. Tries to update Customer 2
Transaction B:
1. Updates Customer 2
2. Tries to update Customer 1
SQL Server jinnota dan iċ-ċiklu u jagħżel transazzjoni waħda bħala l-vittma tad-deadlock. Dik it-transazzjoni tiġi rolled back biex l-oħra tkun tista' tkompli. L-applikazzjonijiet għandhom ikunu lesti li jirċievu żbalji ta' deadlock, u skont l-operazzjoni, li terġa' tipprova tista' tkun xierqa.
Xi modi bażiċi biex tnaqqas id-deadlocks: żomm it-transazzjonijiet qosra, aċċessa r-riżorsi f'ordni konsistenti, evita operazzjonijiet mhux meħtieġa ġewwa transazzjonijiet, oħloq indexes xierqa, qatt tistenna input tal-utent waqt li transazzjoni tkun miftuħa, u implimenta loġika ta' retry għal operazzjonijiet li huma bla periklu li jerġgħu jsiru.
Id-deadlocks ma jistgħux dejjem jiġu eliminati f'sistemi b'concurrency għolja. L-għan huwa li tnaqqas kemm-il darba jseħħu u tittrattahom sew meta jseħħu.
Blocking mhuwiex deadlock
Kont naħseb li dawn huma l-istess ħaġa. Mhumiex.
Blocking isseħħ meta operazzjoni waħda tistenna għax oħra żżomm riżorsa li teħtieġ:
Transaction A updates a product and keeps the transaction open.
Transaction B tries to update the same product.
Transaction B waits until Transaction A finishes.
Dak l-istennija tista' tkun kompletament normali. Deadlock jeħtieġ dipendenza ċirkolari, fejn A jistenna lil B u B jistenna lil A.
Blocking normalment jispiċċa meta l-ewwel transazzjoni tikkommetti jew tirrolja lura. Deadlock ma jistax jissolva b'mod naturali, u għalhekk SQL Server irid jinterrompi waħda mit-transazzjonijiet. Transazzjonijiet li jdumu ħafna jagħmlu l-blocking agħar, għax il-locks jistgħu jinżammu sakemm it-transazzjoni tispiċċa.
Għaliex it-transazzjonijiet jimportaw
Transazzjoni ta' database tgħaqqad diversi operazzjonijiet f'unità loġika waħda ta' xogħol. L-eżempju klassiku huwa li tittrasferixxi flus:
1. Remove $100 from Account A.
2. Add $100 to Account B.
It-tnejn għandhom jirnexxu jew it-tnejn għandhom ifallu. Mingħajr transazzjoni, l-ewwel waħda tista' tirnexxi u t-tieni tista' tfalli, u l-flus jisparixxu.
await using var transaction =
await dbContext.Database.BeginTransactionAsync();
try
{
sender.Balance -= 100m;
receiver.Balance += 100m;
await dbContext.SaveChangesAsync();
await transaction.CommitAsync();
}
catch
{
await transaction.RollbackAsync();
throw;
}
EF Core diġà jgeżwer il-bidliet minn sejħa waħda ta' SaveChanges f'transazzjoni meta l-provider jappoġġaha. Il-kontroll manwali jimporta aktar meta operazzjoni waħda tan-negozju tinvolvi diversi sejħiet ta' SaveChanges, SQL mhux ipproċessat, jew operazzjonijiet oħra li jridu jirnexxu flimkien.
ACID
Transazzjoni affidabbli ssegwi erba' proprjetajiet.
Atomicity tfisser li t-transazzjoni hija unità waħda. Jew l-operazzjonijiet kollha jiġru jew l-ebda waħda. SQL Server ma jridx jiddebita lil min jibgħat mingħajr ma jikkredita lil min jirċievi.
Consistency tfisser li t-transazzjoni tħalli d-database fi stat validu, bil-constraints u r-regoli tan-negozju xorta sodisfatti. Qasam meħtieġ m'għandux isir null, foreign key għandha xorta tippunta lejn rekord eżistenti, bilanċ m'għandux imur negattiv meta n-negozju ma jippermettix hekk.
Isolation tikkontrolla kemm transazzjonijiet konkurrenti josservaw il-bidliet ta' xulxin. Talba oħra m'għandhiex tara l-flus wara li ħallew lil min bagħathom iżda qabel ma laħqu lil min jirċievi.
Durability tfisser li ladarba transazzjoni tikkommetti, il-bidliet tagħha jibqgħu ħajjin anki jekk is-server jikkraxxja wara. SQL Server juża l-log tat-transazzjonijiet tiegħu għal dan.
Il-problema tal-lost update
Din hija l-problema ta' concurrency tad-database li sibt l-eħfef biex nifhem.
Żewġ impjegati jiftħu l-istess rekord ta' klijent, fejn in-numru tat-telefon huwa 1111. L-Impjegat A jibdlu għal 2222. L-Impjegat B, li għadu jżomm il-verżjoni l-qadima, jibdlu għal 3333. A jsalva l-ewwel, imbagħad B jsalva.
Mingħajr iċċekkjar tal-concurrency, l-aġġornament ta' B jikteb fuq dak ta' A, u l-bidla ta' A tintilef mingħajr ma ħadd jiġi mwissi.
Optimistic concurrency
Optimistic concurrency tassumi li konflitti huma possibbli iżda mhux komuni. Minflok tillokkja rekord matul iż-żmien kollu li xi ħadd ikun qed jeditjah, l-applikazzjoni tiċċekkja jekk ir-rekord inbidilx qabel ma ssalva.
Aqra row u l-verżjoni tagħha, ħalliha tiġi modifikata, ibgħat l-aġġornament bil-verżjoni oriġinali, aġġorna biss meta l-verżjoni għadha taqbel, u irrapporta konflitt meta ma taqbilx.
Dan jaqbel ma' applikazzjonijiet web, għax l-utenti ma jżommux locks tad-database miftuħa waqt li jaraw u jeditjaw paġni.
EF Core jappoġġa dan billi jħalli proprjetà tiġi kkonfigurata bħala concurrency token. Matul aġġornament jinkludi t-token oriġinali fil-klawsola SQL WHERE. Jekk proċess ieħor immodifika r-row, xejn ma jaqbel, u EF Core jitfa' DbUpdateConcurrencyException.
rowversion ta' SQL Server
SQL Server jipprovdi t-tip rowversion. Minkejja s-sinonimu antik tiegħu timestamp, mhuwiex data jew ħin. Huwa valur binarju ġġenerat awtomatikament li jinbidel kull darba li r-row tiġi aġġornata, li jagħmlu utli biex tinnota jekk row inbidlitx minn meta qrajtha.
using System.ComponentModel.DataAnnotations;
public class Product
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public decimal Price { get; set; }
[Timestamp]
public byte[] RowVersion { get; set; } = Array.Empty<byte>();
}
L-SQL iġġenerat isegwi din l-idea:
UPDATE Products
SET Price = @newPrice
WHERE Id = @id
AND RowVersion = @originalRowVersion;
Jekk talba oħra diġà aġġornat il-prodott, ir-RowVersion tagħha hija differenti, l-aġġornament jaffettwa żero rows, u EF Core jinnota l-konflitt.
try
{
await dbContext.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
throw new InvalidOperationException(
"This record was changed by another user. Reload it and try again.");
}
F'API reali dan isir tweġiba HTTP xierqa, normalment 409 Conflict. L-applikazzjoni tista' titlob lill-utent jerġa' jgħabbi, turi l-valuri attwali, iħallih jagħżel liema verżjoni jżomm, jgħaqqad bidliet kompatibbli, jew jerġa' jipprova awtomatikament fejn dan huwa bla periklu.
Pessimistic u optimistic
L-istrateġiji tal-concurrency jinqasmu f'żewġ approċċi ġenerali.
Pessimistic concurrency tassumi li konflitt huwa probabbli, għalhekk is-sistema tillokkja r-riżorsa u operazzjonijiet oħra jistennew. Utli meta konflitti jiswew ħafna jew operazzjonijiet iridu jikkoordinaw strettament, bl-ispiża ta' aktar blocking u riskju ta' deadlock.
Optimistic concurrency tassumi li konflitti mhumiex komuni. L-operazzjonijiet jipproċedu mingħajr lock twil, u l-applikazzjoni tivverifika li xejn ma nbidel qabel ma ssalva. rowversion u concurrency tokens ta' EF Core huma l-eżempji komuni.
L-ebda waħda mhi dejjem aħjar. Jiddependi fuq id-dejta, l-għadd mistenni ta' konflitti, u r-regoli tan-negozju.
Il-livelli tal-Isolation
Il-livelli tal-Isolation jikkontrollaw kemm transazzjoni waħda hija iżolata minn oħrajn. Il-livelli ewlenin ta' SQL Server huma READ UNCOMMITTED, READ COMMITTED, REPEATABLE READ, SNAPSHOT, u SERIALIZABLE.
It-tagħlima importanti fil-livell tiegħi hija li isolation aktar b'saħħtu jagħti konsistenza akbar iżda jżid il-blocking u jnaqqas il-concurrency. Isolation aktar baxx itejjeb il-concurrency iżda jista' jħalli transazzjonijiet josservaw dejta b'modi li xi operazzjonijiet tan-negozju ma jistgħux jaċċettaw.
SNAPSHOT u għażliet ibbażati fuq row-version iħallu l-qari juża verżjonijiet ta' row maħżuna minflok jistenna lil min jikteb, li jnaqqas il-blocking bejn qari u kitba. Iżid kunsiderazzjonijiet oħra, u ma jneħħix il-bżonn li tifhem konflitti ta' aġġornament.
Ma nbiddilx il-livell tal-isolation sempliċiment għax applikazzjoni għandha problemi ta' blocking. Il-queries, l-indexes, il-konfini tat-transazzjonijiet, u r-rekwiżit attwali tan-negozju kollha jiġu l-ewwel.
X'nieħu minn dan
Tassumix li linja waħda tfisser operazzjoni atomika waħda, għax _count++ huwa tliet passi u jista' jirrejsja.
Żomm it-transazzjonijiet qosra. Transazzjoni għandha żżomm l-operazzjonijiet tad-database għal azzjoni loġika waħda tan-negozju, u m'għandhiex tibqa' miftuħa waqt li ssejjaħ APIs esterni, tħaddem kalkoli twal, jew tistenna input tal-utent.
Tużax lock ta' C# bħala lock tad-database. Jipproteġi memorja ġewwa proċess wieħed u ma jikkoordina xejn bejn is-servers.
Stenna konflitti. F'sistema b'diversi utenti, żewġ talbiet jaġġornaw l-istess rekord mhix edge case impossibbli, u l-applikazzjoni għandha tiddeċiedi x'jiġri meta jseħħ.
Ifhem ir-regola tan-negozju l-ewwel, għax is-soluzzjoni t-tajba tiddependi fuq l-operazzjoni. Li taġġorna t-titlu ta' blog post mhix l-istess ħaġa bħal li tirriżerva l-aħħar kamra ta' lukanda, tipproċessa pagament, tnaqqas l-inventarju, tittrasferixxi flus, jew tirredimi voucher ta' użu wieħed. Aktar ma l-operazzjoni tkun importanti, aktar bir-reqqa jrid jiġi ddisinjat l-imġiba tal-concurrency tagħha.
Hemm ħafna aktar x'nitgħallem hawn, inklużi tipi ta' locks, execution plans, deadlock graphs, strateġiji ta' retry, u sistemi distribwiti. Dawn il-pedamenti diġà jgħinuni nikteb applikazzjonijiet aktar siguri u ninnota problemi li jidhru biss meta diversi talbiet jaħdmu fl-istess ħin.
Referenzi
- Microsoft Learn, Managed Threading Best Practices: race conditions, synchronization, u
Interlocked - Microsoft Learn, Synchronizing Data for Multithreading
- Microsoft Learn, Deadlocks Guide for SQL Server
- Microsoft Learn, Transaction Locking and Row Versioning Guide
- Microsoft Learn, Handling Concurrency Conflicts in EF Core
- Microsoft Learn, SQL Server Value Generation in EF Core
- Microsoft Learn, Transactions in EF Core
- Microsoft Learn, SET TRANSACTION ISOLATION LEVEL