Többszálú CCC programok

Fórumok

Többszálú programokkal kapcsolatos kérdések helye.

Hozzászólások

Nincs-e valaki olyan helyzetben, hogy többprocesszoros gépen tudná futtatni a tesztprogramokat? Amikor nekem hozzáférésem volt ilyenhez, akkor még nem volt benne a CCC-ben a többszálúság, most meg csak egyprocesszoros gépeim vannak.

--
CCC3

A tutor/multithread-ben az összes programot.

Azokat is amik az x-crash-ben vannak. Itt olyan programok vannak, amik bizonyos (már kijavított) hibák megléte esetén viszonlyag rövid úton elrontják a futtatórendszert. Az x-array0.prg pedig demonstrál egy kijavíthatatlan hibát.

A CCC2-ből a ccctutor/signal-ban levő programokat. Ezek gyötrő programok, amik rengeteg szálon mindenfélét csinálnak, miközben szignálokat is kezelnek. Hogy legyen külső forrásból származó szignál CTRL-C nyomó szerkezetet szoktam használni hozzá (amiért a gyerekeim kiröhögnek).

--
CCC3

Kérünk fényképet a Ctrl-C nyomó szerkezetről, mi is szeretnénk röhögni :D

Windowsos teszteredmények:

array-test.exe: másfél procit eszik, tehát 75%-ra járatja a kétmagos gépet, baja nincs. Át kellett írni a bedrótozott könyvtárnevet. A prociból valszleg a random sleep miatt eszik ilyen ugrálóan.

clid_ext.exe: nem hátalt el, annak megfelelően viselkedik, ahogy le van írva benne.

pi.exe: működik.

threads.exe: szépen futik, praktikusan nem eszik procit, 5% körül, a 0.05 miatt az inkey-ben.

x-crash-ban nincs m.bat.

x-array0: el nem hátalt, sok más jele nincs a működésének.

x-valueassign: nekem jól ment, hagytam elmenni 320-ig, aztán meguntam, nem bír hibázni.

x-valuesort: pont mint valueassign.

CCC2-m nincs, nem is áll szándékomban felrakni.

w

Az x-array0-nak idővel el kell szállnia. Ez a kritikus dolog:

array(1)[1]:=1

Az array függvény csinál egy array-t, amire azonban egyetlen változó sem hivatkozik, ezért a szemétgyűjtés letakaríthatja. Ugyanakkor ennek a tömbnek az első elemébe beírjuk az 1-t. A program ezt az egy utasítást ismételgeti, miközben egy másik szál folyamatosan indítgatja a szemétgyűjtést. Előbb-utóbb el kell szállnia. Az a szerencse, hogy a dolog értelmetlen. Mihelyt értelmet adunk neki, nem is akar elszállni, ez pl. jó:

(x:=array(1))[1]:=1

mert itt az x változó hivatkozik az array-re, tehát nem lesz letakarítva.

A signal-os crash programokat majd átrakom CCC3-ba.

Hogy érzékeltessem a tesztelés nehézségeit leírom, hogy megy a dolog. Veszek egy gyötrő programot, rengeteg szálon véletlenszerű időzítéssel mindenfélét csinál, plusz folyamatosan CTRL-C-t és egyéb szignálokat kezel, miközben folyamatosan gyűjti a szemetet. Hagyom futni egy-két napig. Ha nem száll el, áttérek más gyötrő programra.

Utoljára 2 éve foglalkoztam ezzel, és úgy hagytam abba a tesztelést, hogy nem volt kijavítatlan hiba (kivéve az x-array0 esetet).

Ha hiba van (tipikusan SIGSEGV, deadlock), akkor a direkt debug célra átállított szignál kezelő SIGSTOP-pal megállítja a programot, a gdb-vel pedig meg lehet nézni, hogy hol következett be a leállás. Ilyen próbálgatással lassanként kialakul az érzés, hogy hol vannak kényes pontok, és akkor a gyötrő programot lehet úgy kissebbíteni, hogy jobban a kényes pontra fókuszáljon, miáltal a lefagyásoknak sűrűsödnie kell. Ha már kellően sűrűek a lefagyások, akkor meg is lehet találni a hibát. Volt olyan hiba, amivel több héten át küzdöttem, amíg sikerült úgy besűríteni az előfordulását, hogy ki lehessen javítani.

--
CCC3

Igen, valóban elszállogat Windowson. Ámde most Linuxon tesztelek, és az az érdekesség van, hogy itt gyakorlatilag csak 1 procit eszik meg az x-array0.
array-test.exe szintén.
threads.exe összesen nem eszik meg 10%-ot a két magból. Ha nem nézem a konzolt, amin borul a matéria, akkor ötöt se.
A többi program is hasonlóan viselkedik, amúgy nincs velük gond. Érdekes módon x-array0-t se tudtam elszállítani 4 óra alatt, itthagyom majd éjszakára. Viszont azt kitartóan nem értem, hogy miért nem használják ki a több magot. Én konfiguráltam volna félre valamit?

w

Mi szállogat el? Az x-array0-nak el _kell_ szállnia (előbb-utóbb), a többinek elvileg nem szabadna.

"Viszont azt kitartóan nem értem, hogy miért nem használják ki a több magot."

Tudjuk-e biztosan, hogy a többmagos processzor==több processzor? Én nem tudom. Azt se tudom, hogy többprocesszoros gépen mindig magától kihasználódik-e minden CPU.

Egyprocesszoros gépen az a helyzet, hogy a tesztelési célokhoz képest túl ritkán vált az ütemező a különböző szálak között (Solarison különösen ritkán). Ezért van a programokban itt-ott egy-egy sleep(1), mert abból rájön az ütemező, hogy váltani lehet. Emiatt a CPU kihasználtság nem maximális. A sleepekkel talán érdemes kísérletezni.

A threads.exe esetében a teszt célja csak annyi, hogy nem fogynak-e el a threadid-k. Detach és join nélkül elfogynának. Azonkívül az is látszik, hogy 64 bites rendszeren a threadid-k 64 bitesek, tehát csak P típusú változóban lehet őket tárolni.

--
CCC3

Igen, az x-array0 szállogat el, a többi nem.
Azt tudom biztosan a többmagos processzorról, hogy mind a Linux kernel, mind a Windows XP kernele többprocesszorosnak ismeri fel, és a terhelés szempontjából is így viselkedik, tehát ha mondjuk egy egyszálú alkalmazás beakad, és százra eszi a procit bármi okból, akkor észre sem veszem, ha nincs fenn valami prociterhelés-figyelő, mert a gép egy fikarcnyit sem lassul tőle. Tehát szerintem bátran tekinthetjük annak, főleg, hogy ha jól tudom, akkor gyártástechnológiailag is arról van szó, hogy két processzormagot begyömöszöltek egy tokba.
Majd kísérletezem a sleep-ekkel kicsit később.

w

Leírom pár sorban, mivel szerencsétlenkedtem a napokban.

Elővettem egy korábbi nyúzó (crash) programot, ami a következőket csinálja: Elindít sok szálat (egyszerre 10-15 fut, újak keletkeznek, régebbiek kilépnek), ezek folyamatosan mindenféle string-, array-, objektumműveleteket csinálnak. Egy szál állandóan gyűjti a szemetet, egy szál szignálokat küld a programnak, egy másik (külső) program szintén folyamatosan szignálokat küld, időnként én is nyomkodom neki a CTRL-C-t. A program kezeli a szignálokat, a szignálkezelő szintén string-, array-, objektumműveleteket végez.

Az egésznek az a célja, hogy a változókezelés/szemétgyűjtés hibáit felderítse. Ilyen hibát már évek óta nem találok, vannak viszont deadlockok. Deadlock úgy lehet, hogy a szignálkezelő fennakad egy olyan mutexen, amit ugyanez a szál még a szignál előtt lockolt, hogy elkerülje az összeakadást más szálakkal (Megj: "fast" mutexeket használok, amit ugyanaz a szál sem tud egynél többször lockolni.) Csakhogy a CCC védekezik az ilyen deadlockok ellen: amíg lockolva tart egy mutexet, addig blockolja a szignálokat. Az ilyen szignálok nem vesznek el, csak a mutex lock feloldása után jutnak érvényre. Nem kéne tehát, hogy deadlockok legyenek, mégis vannak, persze nem gyakran, félóránként sikerül egyet-egyet előidézni.

Nekiálltam tehát felderíteni, mi okozza a deadlockokat. Csináltam egy olyan mutex lock/unlock wrappert, ami saját nyilvántartást vezet a mutex lockokról, és deadlock esetén úgy áll meg, hogy a gdb backtrace paranccsal meg lehessen nézni, hogy mi akadt össze. Az eredmény: a libc-beli malloc-ban és free-ben van olyan mutex lock/unlock pár, ami nincs védve a szignálok ellen. Mondjuk a free éppen lockolva tartja a mutexet, amikor szignált kap és elmegy szignált kezelni. A szignálkezelő objektumműveleteket végez, de ehhez meg kell várnia, hogy a másik szálon futó szemétgyűjtés lefusson. Csakhogy a szemétgyűjtés nem tud befejeződni, mert fennakad a free-ben lockolva tartott mutexen.

Nekiálltam tanulmányozni a malloc_hook témát. Látszólag könnyű csinálni olyan hookot, ami végrehajtja az eredeti malloc implementációt, de eközben blockolja a szignálokat. Valóban, ilyen hookokkal a nyúzó órákon keresztül sem tudott deadlockot előidézni. Persze ez nem jelenti azt, hogy a deadlock probléma meg volna oldva, hiszen védtelen mutexek más rendszerhívásokban is lehetnek. Nem is az a cél. A cél a CCC épségének ellenőrzése, a hook csak kiiktat egy ellenőrzést zavaró tényezőt.

Gondoltam, ha már vannak ilyen hookjaim, akkor megszámolom a memóriablokkokat, ezzel ugyanis lehet ellenőrizni, hogy nincs-e memóriaszivárgás. Csináltam egy saját nyilvántartást a memóriablokkokról. A következő "hibákra" derült fény:

Vannak nem lefoglalt, de felszabadított blokkok.
Vannak többször felszabadított blokkok.
Vannak nem felszabadított blokkok, aminek a címét a malloc újra kiadja.

Rögtön megjegyzem, hogy nem a CCC, hanem a nyilvántartás rossz. A hibák úgy keletkeznek, hogy a malloc/free/realloc néha kikerüli a hookot. Egyszálú esetben a hookok 100%-osan működnek, a nyúzóban viszont kb. az esetek 1%-ában a hook nem jut érvényre. Utánanéztem a problémának a gugliban. Egy novelles címről valaki rákérdezett ugyanerre, egy redhates címről azt a választ kapta, hogy a malloc_hook-ot felejtse el, nem jó, nem is lehet megjavítani, és úgyis ki fog kerülni a libc-ből.

Kicseréltem tehát a hookolást LD_PRELOAD-dal felszedett wrapperekre. Ez sokkal jobban működik, mint a hook, de (sokkal kisebb számban ugyan) most is vannak olyan free hívások, amikhez nem tartozik (a wrapper által nyilvántartott) malloc. A hibák most is csak többszálú programban jelentkeznek, ezekben is csak akkor, ha sok új szál keletkezik és szűnik meg. Gyanúm szerint magában a pthread_create-ban lehet olyan malloc, aminek valahogy sikerül kikerülnie a wrappert.

A tanulság:

1) Többszálú programokban nincs jó megoldás (vagy csak én nem ismerem) a malloc/free/realloc hookolására. Tehát ilyen technikára nem lehet pl. szemétgyűjtést alapozni. Szerencsére a CCC nem így működik.

2) A CCC szignálkezelés nem biztonságos. Csak a szerencsén múlik, hogy a szignálkezelő nem akad-e el deadlockban. Gyanítom, hogy a Pythonban ugyanez a helyzet. Persze a gyakorlatban ez nagyon ritkán okoz problémát. A z editor pl. SIGHUP-ra történő kilépés előtt elmenti az editált szöveget, és még soha senki nem észlelt eközben deadlockot.

3) A szignálokra egyedüli 100%-osan biztos reakció csak a teljes ignorálás. A kontrollált kilépés már nem annyira biztos, mert a kilépés folyamata közben bármi előfordulhat.

--
CCC3

Egy kérdésem lenne. Érdemes linuxnak kétmagos gépet venni? Képesek-e már a programok a többszálas futásra? Ha igen, akkor minden disztró tárolójába belerakták-e, vagy manuálisan kell? Elsősorban a konvertáló/rippelő programokra gondolok.