Avatar
aigor
Člen
Avatar
aigor:

Zdravím místní guru,
momentálně se snažím proniknout do vícevláknového programování, ale mám v tom dost nejasností, na které nenecházím odpověď..


class test1
{
static int count_f;
static int count_b;
//static object zamek = new object();

public void start()
{
Console.Write­Line("Start hlavní třídy");
Thread t = new Thread(pocitej);
t.Start();

while (true)
{
if (count_f < 100)
count_f++;
else
break;
//Thread.Sleep(10);
Console.Write­Line("Hlavní vlákno: {0} (v pozadí {1})", count_f, count_b);
}

Console.Write­Line("Konec hlavního vlákna");
if (t.IsAlive)
{
t.Abort();
Console.Write­Line("Vlákno na pozadí bylo přerušeno");
}
else
Console.Write­Line("Vlákno na pozadí bylo ukončeno");
Console.ReadKey();
}

private void pocitej()
{
while (true)
{
if (count_b < 100)
count_b++;
else
break;
//Thread.Sleep(10);
Console.Write­Line("Vlákno na pozadí: {0}", count_b);
}
}
}

Uvedený program generuje něco jako:
Hlavní vlákno: 1 (v pozadí 0)
Hlavní vlákno: 2 (v pozadí 0)
Hlavní vlákno: 3 (v pozadí 1)
Hlavní vlákno: 4 (v pozadí 1)
Hlavní vlákno: 5 (v pozadí 1)
Vlákno na pozadí: 1
Vlákno na pozadí: 2
..

To je mi jasný. Na pozadí se inkrementovala hodnota, ale ještě před vypsáním se přepnulo vlákno. OK.

Ale když pokračuje:
Vlákno na pozadí: 28
Vlákno na pozadí: 29
Hlavní vlákno: 6 (v pozadí 1)
Hlavní vlákno: 7 (v pozadí 30)
Hlavní vlákno: 8 (v pozadí 30)
Hlavní vlákno: 9 (v pozadí 30)
Hlavní vlákno: 10 (v pozadí 30)
Vlákno na pozadí: 30
Vlákno na pozadí: 31

tomu už nerozumím :-(
Při přepnutí do hlavního vlákna nevím proč vidí druhý counter jako 1? Čítač v pozadí už byl několikrát změněn...
Celý příklad je zatím záměrně bez zámků. Teoreticky by přece mělo fungovat vše korektně, když každé vlákno mění jinou hodnotu a druhou pouze čte.
Co mi uniká?

Editováno 1.8.2013 12:24
 
Odpovědět 1.8.2013 12:22
Avatar
Odpovídá na aigor
Luboš Běhounek (Satik):

Vec prvni: doporucuji promennou sdilenou mezi vlakny (i kdyz jen jedno vlakno zapisuje a ostatni ctou) oznacovat vzdy jako volatile, za urcitych okolnosti by ctena promenna mohla vracet stale stejnou hodnotu, i kdyz by se v zapisovacim vlakne jeji hodnota menila.

Vypada to, ze se v hlavnim vlakne z pameti do registru nacte aktualni hodnota promenne count_b - treba je tam dejme tomu hodnota 13, v tu chvili se vlakno uspi (stav registru procesoru se ulozi), rozebehne se vlakno na pozadi, zvysi postupne v nekolika cyklech count_b treba na 20, vlakno na pozadi se uspi, zase se spusti prvni (obnovi se stav z registru, kde je ulozena stara hodnota 13), zavola se vypis do konzole, vypise se 13 a pak se zase v dalsim cyklu tveho while nacte do registru z pameti uz spravna hodnota, takze uz se pak vypisuje spravna.

Nahoru Odpovědět  +1 1.8.2013 13:54
:)
Avatar
Odpovídá na aigor
Luboš Běhounek (Satik):

Ha, tak jsem se nechal oklamat, vypada to, ze problem je stejny, ale v jinem miste, vypada to, ze je to jen ve vypisu konzole :)

Vypis do konzole je ve skutecnosti spousta instrukci, takze to vetsinou vlakno prerusi temer vzdy nekde v jejim volani, takze je tam stara hodnota promenne, protoze int hodnoty se v c# obvykle predavaji hodnotou - takze se jen vytvori kopie hodnoty promenne.

Nahoru Odpovědět 1.8.2013 14:26
:)
Avatar
aigor
Člen
Avatar
aigor:

To mi nedocvaklo, že je tam další xx režie. Taky jsem to zkusil s tím volatile, ale bez efektu, nicméně jako vysvětlení mi to úplně stačí.

Díky ;)

 
Nahoru Odpovědět 1.8.2013 14:47
Avatar
aigor
Člen
Avatar
aigor:

Mám ještě druhou začátečnickou otázku...

Když v kódu ošetřím kritické části následovně

static object zamek = new object();
..
..

lock (zamek)
{
// inkrementace číteče
// výpis stavu čítače
}

pak počítání funguje přesně. Jen pořád nerozumím konstrukci "lock".
Jak třída pozná, že chci uzamknout proměnné cnt_f,cnt_b,... když jako parametr zámku uvedu obecný objekt :o
Chápal bych něco jako lock(this) pro celou třídu, nebo lock(a,b) pro dané proměnné...

 
Nahoru Odpovědět 1.8.2013 14:56
Avatar
Kit
Redaktor
Avatar
Odpovídá na aigor
Kit:

A tohle má být co?

private void pocitej()
{
while (true)
   {
   if (count_b < 100)
      count_b++;
   else
      break;
   //Thread.Sleep(10);
   Console.WriteLine("Vlákno na pozadí: {0}", count_b);
}

To neumíš napsat normální cyklus?

private void pocitej()
{
while (count_b < 100)
   {
   count_b++;
   //Thread.Sleep(10);
   Console.WriteLine("Vlákno na pozadí: {0}", count_b);
}

Volatile na všechno nestačí. Lepší je používat thread-safe metody a kolekce.

Nahoru Odpovědět 1.8.2013 14:59
Vlastnosti objektů by neměly být veřejné. A to ani prostřednictvím getterů/setterů.
Avatar
Odpovídá na Kit
Luboš Běhounek (Satik):

Volatile na všechno nestačí, ale když zapisuje jen jedno vlákno a ostatní vlákno/vlákna jen čtou, tak obvykle není potřeba nic synchronizovat.

Nahoru Odpovědět 1.8.2013 15:11
:)
Avatar
Odpovídá na aigor
Luboš Běhounek (Satik):

lock nezamyká objekty, ale části kódu.

Části kódu, které jsou pozamykané stejným zámkem nikdy nebudou probíhat najednou, (ani když ve dvou vláknech spustíš ten samý kód, tak není možné, aby ty zamčené části běžely najednou).

Nahoru Odpovědět 1.8.2013 15:14
:)
Avatar
aigor
Člen
Avatar
aigor:

ad Kit:
není třeba se pohoršovat, ten kód je pouze testovací a cykly jsem postupně zapisoval několika způsoby, když jsem ladil přepínání vláken..

ad Satik:
volatile jsem doteď ani nikdy nepoužil, v normální literatuře (basic skill) se o ní zmiňují sotva okrajově, pokud vůbec.

"Části kódu, které jsou pozamykané stejným zámkem nikdy nebudou probíhat najednou"
Princip zámku je mi jasný, nerozumím syntaxi. Pokud by to bylo jak říkáš, pak by se zámek používal spíš s parametrem "jméno", nebo ne?

V CZ překladu na http://www.albahari.com/threading/ str.21 autor píše "V příkladu nahoře chráníme obsah metody Go(), a tím pádem i obsah proměnných val1 a val2" (totéž co u mě lock, count_f a count_b). Ta formulace mi vůbec nedává smysl :(

 
Nahoru Odpovědět 2.8.2013 7:38
Avatar
Odpovídá na aigor
Luboš Běhounek (Satik):

Volatile se používá kvůli tomu, že překladač občas používá nějaké optimalizace, které můžou ve vícevláknových aplikacích nadělat problémy - například dejme tomu, že máš proměnnou
cekat typu bool nastavenou na true
a kód

while (cekat)
{
  nejaky kod, ktery promennou cekat vubec nepouziva
}

pokud proměnná cekat není volatile a překladač tedy neví, že se její obsah někde jinde může změnit, tak se ten cyklus může zoptimalizovat na rychlejší variantu:

if (cekat)
  while (true)
  {
    ...
  }

a pak by změna hodnoty proměnné cekat na false v jiném vlákně ten cyklus nezastavila.

Ten parametr u zámku - zamykací objekt se právě používá místo jména.

"V CZ překladu na http://www.albahari.com/threading/ str.21 autor píše "V příkladu nahoře chráníme obsah metody Go(), a tím pádem i obsah proměnných val1 a val2" (totéž co u mě lock, count_f a count_b)"

  • Tím asi bylo myšleno, že díky tomu, že je chráněna ta část kódu, je vlastně chráněn i obsah těch proměnných, protože se třeba nikde jinde jejich obsah už nemění.
Nahoru Odpovědět  +1 2.8.2013 9:40
:)
Avatar
aigor
Člen
Avatar
aigor:

Aha, už je mi to o dost jasnější. U lock() jsem pořád hledal souvislost mezi pojmenovaným objektem a proměnnýma, který chci zamknout...

 
Nahoru Odpovědět 2.8.2013 10:34
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 11 zpráv z 11.