← l-artikli kollha

.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.

Kif Nifhem il-Concurrency f'.NET

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