Írtam egy active record / ORM rendszert. Ennek a rendszernek - amennyire lehet - függetlennek kell lennie a felhasználótól, viszont sokszor igényel(het) adatbázis kapcsolatot, és nem csak egy helyről. Ha mindenhol új kapcsolatot nyitok, az iszonyatosan sok időbe telhet, ezért elhatároztam, hogy statikus kapcsolatot fogok használni.
A problémám:
Weben minden kérés külön szálon érkezik. Ez persze asztali alkalmazásnál is előfordulhat. Hogy thread safe legyen, azt találtam ki, hogy minden szálhoz rendelek egy kapcsolatot, egy Dictionary<Thread, OdbcConnection> -nel, ami igény esetén - ha még nem létezik a szálhoz kapcsolat - automatán példányosítja és megnyitja azt, majd visszaaadja. Ha már létezik, akkor csak visszaadja. A Thread "azonosítása" a Thread.CurrentThread által történik.
Persze ezt meg is kell semmisíteni minden esetben, erre pedig azt gondoltam ki, hogy csináltam egy osztályt, ami IDisposable, és csak annyit tesz, hogy a Dispose metódusa a statikus, adott szálhoz rendelt adatbáziskapcsolatot Dispose-olja, és kiveszi a Dictionary<Thread, OdbcConnection>-ből.
Ezt utána a következőképpen lehet használni (ha a statikus Osztaly.Kapcsolat egy új példányt ad vissza ebből az IDisposable osztályból):
using(var conn = Osztaly.Kapcsolat)
{
// ezen a szálon lehet mindenfélét csinálni, lesz hozzá kapcsolat
}
// itt pedig mindenképp meg lesz szüntetve a kapcsolat
Kérdések: helyesen járok-e el? Biztosan biztonságos ez, vagy történhetnek váratlan dolgok? Mennyire antipattern ez a megoldás?
- 5495 megtekintés
Hozzászólások
Poolozd a kapcsolatokat, hozz letre N darabot, rakd oket egy taroloba es innen "kolcsonozz" kapcsolatot, amikor szukseged van ra. Hasznalatban levo kapcsolathoz mas thread meg nem nyul hozza, amig vissza nem adod a poolnak.
Feltetelezem, hogy tanulasi cellal talalod fel a kereket, masert nemigen van ertelme...
----------------------
"ONE OF THESE DAYS I'M GOING TO CUT YOU INTO LITTLE PIECES!!!$E$%#$#%^*^"
- A hozzászóláshoz be kell jelentkezni
Tanulmányi célból készült, de később is szeretném felhasználni. Ezt a connection poolingot megtaláltam már, de:
- nem tudom hogy ha nem saját szerveren megy a dolog, akkor lehetséges-e (nem férek hozzá a beállításaihoz). Amit eddig találtam, ott registryben kellett turkálni. Vagy ezt teljesen saját implementációval kellene megoldani?
- MySQL-hez készült, szintén a fentebb említett okokból ODBC connectorral, mivel a Connector/NET-et tiltja a biztonsági szint. Azzal hogy működik ez?
- nem biztos hogy megoldás a problémámra :)
Lehet, hogy ez az egész egy nagy tervezési hiba, de azt találtam ki, hogy mondjuk definiálok egy User osztályt. Annak a mezőire beállítja a felhasználót lekérő adatbázis eredmény mezőit. Ha definiálok egy Post objektumot, aminek van egy szerzője, akkor egy eseménykezelőben, miután a lekérdezés megtörtént (tehát nem join-nal), le tudja kérni a megfelelő User-t, egy belső User objektumként.
A probléma ott kezdődött, hogy ha mondjuk van 1000 db Post, amiben van 2 db ilyen belső objektum, és én mindegyiknek külön kapcsolatot adok, akkor mivel minden belső lekérdezés független a külsőtől, 1+2*1000 db kapcsolat kell (minden Post objektumhoz 2 további lekérdezés tartozik), pedig elég lenne összesen 1 db is. Az gondolom érzékelhető, hogy ha 1-1 sor lekéréséhez (PK alapján) külön kapcsolatokat hozok létre, akkor a kapcsolódás és az objektumok létrehozása több időbe fog telni mint a lekérések. (Persze ezt jobb join-nal, és sima DataReader-rel megoldani.)
Ha a felhasználóra bízom hogy adjon 1 db kapcsolatot, akkor azt mindenhol keringetni kell a rendszerben, ami így elég bonyolultan összefüggővé válik. Ezért jobb lenne, ha 1 db kapcsolattal szolgálna ki 1 db embert, és kész. Ilyenkor viszont szálanként kell kezelni, és biztosítani is kell hogy ne maradjon meg örökké a statikus kapcsolat.
Szóval ezzel küzdöttem eddig, mert ha ezt meg tudom oldani, akkor a többi része eléggé tetszik nekem.
Még egy kérdésem felmerült az előbb: A Dictionary, ha a kulcsai a szálak, és még a szál futása alatt eltávolításra kerül az elem, akkor biztonságos? Mert van ConcurrentDictionary is.
- A hozzászóláshoz be kell jelentkezni
Kapcsolatok menedzsmentje semmikepp sem az ActiveRecord implementadio feladata, annak csak keregetnie kene a kapcsolatokat egy kulso DataSource-felesegbol. .NET-hez nem ertek, ezert sajnos konkretumokkal nem tudok szolgalni.
de később is szeretném felhasználni
Szerintem ne szivasd magad, hacsak nem alkotsz valami igazan zsenialisat (kerdeseid alapjan ezt nem latom annyira valoszinunek a kozeljovoben :^)), a te implementaciodnak nem lesznek elonyei a mar elerheto 3rd party cuccokhoz kepest, es nem is lesz annyira stabil (a 3rd party ORM libeket ugye tobben hasznaljak es tesztelgetik...)!
----------------------
"ONE OF THESE DAYS I'M GOING TO CUT YOU INTO LITTLE PIECES!!!$E$%#$#%^*^"
- A hozzászóláshoz be kell jelentkezni
Van ennek alapja, egy létező rendszer, amit PHP-ban írtak (a CodeIgniter AR implementációja nagyon megtetszett).
Én szerettem volna ugyanazt .NET-ben használni, de még hasonló elvűt sem találtam, ezért írtam sajátot. Csak itt ugye nem olyan egyszerűek a dolgok mint ott, nem elég a szkript elején egy mysql_connect(), a végén meg úgyis mindegy, de inkább zárjuk be, hanem oda kell figyelni a kapcsolatokra, ráadásul a statikus változók a szerveren mindenkinek megosztottak. És mivel a C# erősen típusos, más lehetőségek vannak, mint PHP-ban.
- A hozzászóláshoz be kell jelentkezni
Van egy par AR implementacio .NET-re, probaltad az osszeset..?
----------------------
"ONE OF THESE DAYS I'M GOING TO CUT YOU INTO LITTLE PIECES!!!$E$%#$#%^*^"
- A hozzászóláshoz be kell jelentkezni
Nem. Nem úgy működnek ahogy szeretném. Ez már a readme-ből is ki tud derülni.
A komolyabb dolgokkal kapcsolatban az az első véleményem, hogy én SQL nyelven szeretném használni, nem HQL vagy LINQ nyelven. Nem akarom leírni az összes kapcsolatot XML fájlokba, nem akarok automata összerendelést az Orders és az Order között, nem akarok 3 táblához 1500 sor generált kódot, nem akarok semmilyen bonyolítást, csak annyit szeretnék, hogy ha azt mondom, hogy Rendszerem.Insert("users", this), akkor szúrja be a users táblámba az objektumomat, és hasonló dolgok.
- A hozzászóláshoz be kell jelentkezni
Gyári Entity model framework miért nem jó?
--
"SzAM-7 -es, tudjátok amivel a Mirage-okat szokták lelőni" - Robi.
- A hozzászóláshoz be kell jelentkezni
Nem tudom az ODBC tud-e ilyet: Using Connection Pooling with SQL Server
- A hozzászóláshoz be kell jelentkezni
Épp most kérdeztem rá erre :)
http://hup.hu/node/95858#comment-1166213
- A hozzászóláshoz be kell jelentkezni
Az Add-hoz és a Remove-hoz mindenképp lock kell a Dictionary-re, olvasni bátran szabad.
- A hozzászóláshoz be kell jelentkezni
Ha jol tevedek, a Dictionary nem thread safe, ezert ha csak iraskor szinkronizalsz, akkor inkonzisztens allapotban lehet, amikor olvasol.
----------------------
while (!sleep) sheep++;
- A hozzászóláshoz be kell jelentkezni
Hm. Próbálgattam sokat, gyakorlatban működött ez a változat. Ezt írják egyébként:
"A Dictionary can support multiple readers concurrently, as long as the collection is not modified. Even so, enumerating through a collection is intrinsically not a thread-safe procedure. In the rare case where an enumeration contends with write accesses, the collection must be locked during the entire enumeration. To allow the collection to be accessed by multiple threads for reading and writing, you must implement your own synchronization."
Rosszul értem?
- A hozzászóláshoz be kell jelentkezni
statikusként threadsafe?
ha nem, akkor irj hozza wrappert.
--
"SzAM-7 -es, tudjátok amivel a Mirage-okat szokták lelőni" - Robi.
- A hozzászóláshoz be kell jelentkezni
Azt írja hogy az olvasás threadsafe, az írás nem az. Minden írással (módosítással) kapcsolatos művelethez lockolok. Bonyolítja a helyzetet, hogy a Dictionary belülről egy HashTable. Ez az elemek kiválasztásának menetét nagyban befolyásolja, mert így nem kell enumerálni. A SortedDictionary-vel például ugyanez nem működik, az leáll a tesztprogramommal is.
- A hozzászóláshoz be kell jelentkezni
Nem, nem, nem. Akkor nem kell lockolnod az olvasast, ha kozben biztosan nem irja senki. Sok olvaso olvashatja, de csak akkor, ha read only a kollekcio. Ha olvassak es irjak is, akkor mindkettot lockolni kell. Ez teljesen alapveto.
----------------------
while (!sleep) sheep++;
- A hozzászóláshoz be kell jelentkezni
Logikusnak hangzik, csak ez az egész olyan furcsán túl jól működik.
Ha nem lockolok az írásra, hamar elszáll (out of bounds és egyebek, tehát valaki módosítja épp a gyűjteményt). Az elképzelhetetlen, hogy mivel a kiválasztás O(1)-es, és egyedi hash alapján történik, akkor is helyes eredményt ad, ha egyvalaki közben módosítja? Főleg ha ki van zárva hogy azt keressük amit épp valaki módosít?
Szerintem végtelenítem és futva hagyom éjszakára, kíváncsiságból.
- A hozzászóláshoz be kell jelentkezni
Mit modosit, a kollekciot, vagy a kollekcioban tarolt ertekeket?
----------------------
while (!sleep) sheep++;
- A hozzászóláshoz be kell jelentkezni
A kollekciot.
Egesz ejjel futott a cucc (es meg mindig fut), hiba nelkul.
Szerintem ez a hash-sel fugghet ossze. Talan amig egyedileg megtalal mindent hash alapjan addig nem gond ha tobb az olvaso es van max egy iro, mivel nincs enumeracio. Ha utkozesfeloldas lenne, akkor lenne baj.
- A hozzászóláshoz be kell jelentkezni
Semmit nem bizonyit a teszted, csak azt, hogy nalad ugy jottek ki az idozitesek, hogy nem lett belole baj. Masik gepen barmikor elhasalhat.
Nem hiszem, hogy olyan bonyolult lenne megerteni, hogy senki nem garantalja, hogy mikozben az olvaso eppen a kollekcio adatstrukturait olvassa, az iro nem fogja eppen pont azokat a strukturakat atirni. Egyreszt nagyon nagy hibat kovetsz el, ha igy hasznalod, masreszt a helyedben elgondolkoznek ezen, mert ha neked ez komolyan kerdes, akkor nem sajat ActiveRecord rendszert kellene irnod, hanem vegigragni az alap parhuzamossagi problemakat. No offense, konstruktiv kritikanak szanom.
----------------------
while (!sleep) sheep++;
- A hozzászóláshoz be kell jelentkezni
Igazad van, valóban végig kell rágnom őket. Épp annak az elején járok egyébként. Csak nehéz ehhez segítséget találnom.
Nem használnám így a dolgot jelen helyzetben. Ha tudnám hogy igaz vagy nem amit mondok, akkor használnám vagy nem, de így, hogy favágó módszerrel tesztelem, nem fogom élesben használni semmiképp. Amit írtam az csak egy ötlet volt, hogy miért nem hasalt el (még mindig fut egyébként). Tehát feltételeztem emellett azt is, hogy ha két hash megegyezne, akkor már történhetne baj.
Sose kerültem még ilyen problémával szembe, hogy - ráadásul szerveren - statikus erőforrást akartam megosztani szálak között, minél kisebb lock-kal. Kölcsönös kizáráshoz (élesben) meg termelő-fogyasztóhoz (próbaképp) volt már szerencsém, de ez a mostani dolog nem egyszerű kérdés, ha garantáltnak kell lennie a helyes működésének, és jó volna ha gyors is lenne. Arról nem is beszélve, hogy a szál fölött, amihez az erőforrást biztosítani akarom, semmilyen hatalmam sincs, ráadásul a threadpool-ban van, ezért még az is esetleges, hogy leáll-e valaha.
- A hozzászóláshoz be kell jelentkezni
Akkor a kovetkezo dolgoknak meg nezz utana
- lock-free containers (vagy lock-free data structures)
- volatile jelentese
- lock, mutex kulonbseg
Meg irj ide, ha van valami kerdes, aztan megtargyaljuk.
----------------------
while (!sleep) sheep++;
- A hozzászóláshoz be kell jelentkezni
A lock is egyfajta mutex, nem? Ami a párhuzamos végrehajtást zárja ki a kód megjelölt részein. Egyéb mutexek pedig más dolgokat zárnak ki, például szemaforokkal valamilyen közös erőforrást védenek adott ideig vagy adott esemény bekövetkezéséig (pl. kapcsolat vagy file bezárása).
- A hozzászóláshoz be kell jelentkezni
Tehát itt a kód: http://hup.pastebin.com/9iVtWUna
Cseréld le SortedDictionary-re, és már nem megy. Ahhoz az egész Add metódust lock-olni kell. Nekem nyilván célom minél kisebb lock-ot csinálni.
Szerk.: egy ilyen teszt elég favágó módszer, úgyhogy továbbra is megerősítés needed.
Szerk2: az if (!dict.ContainsKey(Thread.CurrentThread)) ellenőrzés fölösleges, csak az eredeti algoritmusból indultam ki és benne maradt
- A hozzászóláshoz be kell jelentkezni
azon filózom, hogy refcount is kéne, hogy ne törölhesd azt a connectiont a poolból amit amúgy használsz.
leghamarabb holnap reggel tudom kiprobalni a kodot.
--
"SzAM-7 -es, tudjátok amivel a Mirage-okat szokták lelőni" - Robi.
- A hozzászóláshoz be kell jelentkezni
Az, hogy neked mukodott, nem jelent semmit.
> A Dictionary can support multiple readers concurrently, as long as the collection is not modified
Errol beszelek. Ha van writer, akkor readereket is lockolni kell. Semmit nem er, ha csak a writereket lockolod.
----------------------
while (!sleep) sheep++;
- A hozzászóláshoz be kell jelentkezni
ReaderWriterLockSlim-nek nézz utána. Ha csak olvasási lock van felvéve, gyakorlatilag nem blokkol, ha van írási is, akkor lock-ként működik. Nagyvonalakban. Cserébe kb 1,7-szer lassabb a locknál.
- A hozzászóláshoz be kell jelentkezni
Ez erdekesnek tunik, koszi.
- A hozzászóláshoz be kell jelentkezni
Szerk.: hulyeseget irtam, azt hittem, hogy a sima RWLockhoz hasonlitod.
andrasf: hasznalhatsz RWLS-t, viszont _mindig_ lockolj, olvasasnal is, csak akkor read lockot kerj. Viszont vigyazz az ehezesre (sok reader eseten a writerek esetleg nem jutnak hozza az eroforrashoz).
----------------------
while (!sleep) sheep++;
- A hozzászóláshoz be kell jelentkezni
És arra hogyan vigyázzak? Ezen a területen semmi sem lehet egyszerű? :)
- A hozzászóláshoz be kell jelentkezni
Sajnos nem.:(
----------------------
while (!sleep) sheep++;
- A hozzászóláshoz be kell jelentkezni
Sőt, ez nagyon jónak tűnik. Elég "szabadonfutónak" tűnnek így a szálak, nem egymás után vannak sorban a műveletek, ráadásul ezt tényleg nem sikerül megfektetni semmilyen adatszerkezettel, úgyhogy a favágó bizonyítás is működik rá :)
- A hozzászóláshoz be kell jelentkezni