[C++] watki, rand, srand, cout - crash

0

od poczatku.. ktos zamiescil pytanie w dziale newbie http://4programmers.net/Forum/viewtopic.php?id=120986 i postanowilem to sprawdzic.. skrobnalem na szybko program odpalajacy N watkow, czekajacy pomiedzy kolejnymi odpaleniami pewien kawalek czasu. kazdy pojedynczy watek mial za zadanie wylosowac i wypisac M liczb

kod wyglada jak wyglada, bo mam drobne zboczenie o nazwie 'boost' i lubie sie bawic.. :)

#include <ctime>
#include <iostream>
#include <boost/thread.hpp>
#include <boost/smart_ptr.hpp>
#include <boost/lambda/lambda.hpp>
#include <boost/lambda/bind.hpp>
#include <boost/lambda/construct.hpp>
#include <boost/thread/mutex.hpp>

struct guarded_ostream //JEDEN
{
	boost::mutex guard;
	std::ostream& ostr;
	guarded_ostream(std::ostream& ostream):ostr(ostream){}
};

struct letter
{
	char const chr;
	guarded_ostream& out;
	letter(char const c, guarded_ostream& output):chr(c),out(output){}
	void print_thread()
	{	using namespace std;
		using namespace boost;

		srand((int)std::time(0));  //ZERO
		
		unsigned long x = 3;
		do
		{	
			xtime t;
			xtime_get(&t, TIME_UTC);
			t.sec += 1;      //TRZY
			thread::sleep(t);        //TRZY
			mutex::scoped_lock lock(out.guard);  //JEDEN, PIEC
			out.ostr << chr << ':' << rand() << endl;
		}while(--x);
		{	mutex::scoped_lock lock(out.guard);  //JEDEN, PIEC
			out.ostr << chr << ':' << "!" << endl;		//CZTERY
		}
		delete this;
	}
};

int main()
{
	using namespace std;
	using namespace boost;
	using namespace boost::lambda;
	
	//srand((int)std::time(0));  //ZERO

	string const s = "12345";
	thread_group threads;
	letter* tmp;
	guarded_ostream go(cout);
	boost::xtime t;
	for_each(s.begin(), s.end(),
	(	var(tmp) = bind(new_ptr<letter>(), _1, var(go)),
		bind(&thread_group::add_thread, var(threads),
			bind(new_ptr<thread>(),
				unlambda(bind(&letter::print_thread, var(tmp)))
		)	),
		bind(&xtime_get, &t, TIME_UTC),
		var(t.nsec)+=100 * 1000 * 1000, //100 milisekund //DWA
		bind(&thread::sleep, var(t))
	)	);

	threads.join_all();
	
	cin.get();
}

przykladowy wynik:

1:17565 //<----
2:17565 //<----
3:17568 //<---- !!
4:17568 //<----
5:17568
1:18028
2:18028
3:28776
4:28776
5:28776
1:25209
1:!
2:25209
2:!
3:10306
3:!
4:10306
4:!
5:10306
5:!

pewne rzeczy tutaj sa dziwne i nadmiarowe.. juz wyjasniam

srand'a mozna postawic w dwoch miejscach, oznaczonych ZERO. sporo ludzi odruchowo stawia je sie na starcie programu, co jak odpisalem autorowi tamtego postu - jest bledem, bo kazdy watek ma swoj seed RNG i na starcie go dziedziczy z watku glownego.. jesli postawic srand na starcie glownego watku - kazdy RNG bedzie idealnie zsynchronizowany z reszta. tak wiec srand musi byc odpalony na starcie kazdego watku z osobna. ten program to pokazuje

po napisaniu programu zauwazylem ze napisy generowane przez watki sa "posiekane".. no tak, przeciez COUT nie jest synchronizowany.. tak wiec powstalo JEDEN -- tylko po to zeby przekazac ten sam mutex do kazdego watku

sleepy w miejscach oznaczonych DWA i TRZY sa bardzo wazne - DWA rozsuwa odpalenie watkow w czasie, TRZY - gwarantuje, ze watki sie beda przecinac (tzn. nie bedzie przypadkowej serializacji, ze kazdy watek zdazy sie zakonczyc przed startem nastepnego). u mnie, jesli DWA jest ustawione na 100milisekund (tak jak jest) to co ktores odpalenie programu pokaze ze generatory sie 'rozjada'. oznaczylem to miejsce na przykladowym wyniku. czasem - owe 100ms nie wystarczy aby sie rozjechaly - to normalne !!!

ok.. mam nadzieje ze juz wszystko wiadomo, new_ptr i delete this chyba mi nikt nie zarzuci :) mam jedno spostrzezenie-ciekawostke i jedno pytanie:

ciekawostka: ten program ladnie pokazuje, ze pierwsza liczba uzyskana po srand nie jest losowa, tylko jest wprost zalezna od czasu. widac to nawet po zalaczonym wyniku programu :) jesli ustawic DWA np. na sekunde, to kazdy kolejny RNG bedzie mial pierwsza liczbe bardzo podobna, ale o pare oczek wyzsza. potem (na szczescie) juz sie rozjezdzaja

pytanie: gdzies w tym programie mam ... blad. pierwotnie sie walnalem i w miejscu DWA mialem napisane 100 * 000 * 1000, czyli zero :) a w miejscu TRZY mialem nsec zamiast sec.. efekt jest oczywisty - wszystko pedzilo bez zadnego oczekiwania. i w takim ukladzie, raz na jakis czas program wyklada sie w miejscu CZTERY, konkretniej mowiac - referencja OUT pokazuje na smieci (tzn. &out == 0xfeeefeee), tak jakby obiekt *this zostal zwolniony -- co jest nie mozliwe.. siedze nad tym od godziny i nie widze gdzie mam blad. wszystko jest zsynchronizowane.. no i nie ma szansy aby delete this wykonal sie wczesniej.. po zwiekszeniu ilosci watkow do 10 program zaczal sie wywalac takze w miejscach PIEC, z tego samego powodu. co to moze byc? sprawdzalem - threadgroup.joinall() dziala prawidlowo, nie puszcza za wczesnie..

0

dalej nie znalazlem.. juz mnie to bodzie rowno. albo jestem slepy albo to jest nietrywialne

0

Ranides - ok, przyznam, sluszna zjebka :) ale mnie wnerwil, bo nawet nie podal w jakim stylu chce te watki zobaczyc.. wiec mu podrzucilem kod na 1 strone :P

a co do w/w zlambdzenia i zbindowania.. ja juz tak od dluzszego czasu i musze przyznac ze mimo ze skladnia miejscami powala, to ten sposob na prawde mi sie podoba i .... paradoksalnie pozwala uniknac masy bledow. jak juuz sie skompiluuuujeeee to bledy innego typu bardzo rzadko zostaja..

..ale tutaj totalnie mnie to zagielo. autentycznie, zaczynalem juz podejrzewac ze cos jest z tym srand albo time wiec wokol nich zaczalem locki stawiac, ni huhu, to samo.. jak bede mial chwilke to "odwine" ten kod do "zdelambdaizowanej i zdebindowanej" formy i bede dalej siedziec. bo kuźwa nie wyrobie jak nie znajde buga!!!

0

Mam C2D, linuksa 2.6.22 SMP, g++ 4.1.3, libboost-thread 1.34.1.

Oto moje przykładowe wyniki:

1:1233964126
2:1836314331
3:894319026
4:1028806994
5:512442977
1:806539502
2:1964417871
3:541776155
4:626909474
5:669525511
1:1785286676
1:!
2:383551844
2:!
3:304400611
3:!
4:507245268
4:!
5:1615660780
5:!

Program odpalałem po 5 razy dla 5 wątków, 10 i 100 wątków.
0 błędów wykonania.

Ale dla 100 wątków, część wyników się na początku powtarza:

0:468505054
1:169853867
2:169853867
3:169853867
4:169853867
(..)

0

odpalales w wersji z opoznieniami czy bez..? to pierwsze wyglada na z opoznienaimi, a drugie na bez. tak wlasnie mialo byc, zeyb pokazac ze trzeba rozsuwac watki w czasie jesli sie chce srand(time()) robic.

a progz sie srednio (na WinXPsp2, vs2005, x86) wywala raz na 10-15 odpalen, mogles nie trafic przy pieciu.

notka: od razu widac ze RNG na linuksie jest lepszy.. liczby pelne 32bit i nie ma zaleznosci od czasu w pierwszym losowaniu:)

0

W obu przypadkach odpalałem z takimi samymi opóźnieniami(tak jak jest w Twoim programie).
Teraz nie mam czasu, ale wieczorem zrobię więcej testów.

0

problem wystepuje w momencie usuniecia opoznien - czyli "wylaczenie" miejsc DWA i TRZY:) z opoznieniami wszystko pieknie gra. pozniej? spox. zero pospiechu. dzieki za zainteresowanie:)

0

panowie, znalazlem.. ponizszy kod sie nie wywala

.. chyba ze odkomentowac delete this; wtedy wywala sie niemal zawsze.

#include <ctime>
#include <iostream>
#include <boost/thread.hpp>
#include <boost/smart_ptr.hpp>
#include <boost/lambda/lambda.hpp>
#include <boost/lambda/bind.hpp>
#include <boost/lambda/construct.hpp>
#include <boost/thread/mutex.hpp>

struct guarded_ostream
{
	boost::mutex guard;
	std::ostream& ostr;
	guarded_ostream(std::ostream& ostream):ostr(ostream){}
};

struct letter
{
	char const chr;
	guarded_ostream& out;
	letter(char const c, guarded_ostream& output):chr(c),out(output){}
	void print_thread()
	{	using namespace std;
		using namespace boost;

		srand((int)std::time(0));

		unsigned long x = 3;
		do
		{	
			xtime t;
			xtime_get(&t, TIME_UTC);
			t.nsec += 1;
			thread::sleep(t);
			mutex::scoped_lock lock(out.guard);
			out.ostr << chr << ':' << rand() << endl;
		}while(--x);
		{	mutex::scoped_lock lock(out.guard);
			out.ostr << chr << ':' << "!" << endl;
		}
		//delete this;   //<--- WINOWAJCA. why?
	}
};

int main()
{
	using namespace std;
	using namespace boost;
	using namespace boost::lambda;
	
	//srand((int)std::time(0));

	string const s = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
	thread_group threads;
	letter* tmp;
	guarded_ostream go(cout);
	boost::xtime t;
	for_each(s.begin(), s.end(),
	(	var(tmp) = bind(new_ptr<letter>(), _1, var(go)),
		bind(&thread_group::add_thread, var(threads),
			bind(new_ptr<thread>(),
				unlambda(bind(&letter::print_thread, var(tmp)))
		)	),
		bind(&xtime_get, &t, TIME_UTC),
		var(t.nsec)+=100 * 000 * 1000,
		bind(&thread::sleep, var(t))
	)	);

	threads.join_all();
	cout << "*" << flush << endl;
	cin.get();
}

pozostaje pytanie - czemu? przeciez po utworzeniu watku jedynie on korzysta ze wskaznika na 'letter', i delete this jest dopiero na samym zakonczeniu watku:/

0

rozwinalem foreacha:

	//for_each(s.begin(), s.end(),
	//(	var(tmp) = bind(new_ptr<letter>(), _1, var(go)),
	//	bind(&thread_group::add_thread, var(threads),
	//		bind(new_ptr<thread>(),
	//			unlambda(bind(&letter::print_thread, var(tmp)))
	//	)	),
	//	bind(&xtime_get, &t, TIME_UTC),
	//	var(t.nsec)+=100 * 000 * 1000, //HERE
	//	bind(&thread::sleep, var(t))
	//)	);
	string::const_iterator it = s.begin(), end = s.end();
	while(it!=end)
	{	tmp = new letter(*it, go);
		threads.add_thread(new thread(bind(&letter::print_thread, var(tmp))));
		xtime_get(&t, TIME_UTC);
		t.nsec+=100 * 000 * 1000;
		thread::sleep(t);
		++it;
	}

i w tym przypadku tez sie wypieprza. aale.. jesli tutaj podmienic

unlambda(bind(&letter::print_thread, var(tmp)))

na

unlambda(bind(&letter::print_thread, tmp))

(wywalic VAR) to wszystko jest ok.

no tak. tylko tu, po rozwinieciu foreach, mozna sobie VAR wywalic. ale w oryginale -- nie, bo wszystkie watki dostana ten sam ptr i dopiero bedzie ubaw..

nie rozumiem jednak, czemu to VAR mialo by w ogole szkodzic. przeciez ono jest tworzone przed uruchomieniem watku, niszczone - przed lub po zalezy od timingu. zreszta - ono tylko trzyma ref na tmp, a konstruktor letter robi z niego kopie do lokalnego atrybutu jeszcze przed uruchomieniem watku... kurde.. moze unlambda jakos koliduje.. zdurnialem

0

Czytam ten topik i czytam i mam wrażenie, że albo ja sobie coś ubzdurałem, albo autor tego tekstu:

kazdy watek ma swoj seed RNG i na starcie go dziedziczy z watku glownego

Na jakiej powstało to stwierdzenie nie mam pojęcia. W każdym razie mi zawsze się wydawało, że rand() ze standardowej biblioteki posiada JEDNĄ zmienną (globalną), w której pamięta zalążek (ziarno, seed) generatora.
Skąd więc jednakowe wyniki? Proste z konfliktu dostępu wątków do tej zmiennej zalążka (ziarna, seed). Zanim pierwszy wątek zapisał nową wartość następny odczytywał starą wartość i stąd wrażenie powtarzalności.

Moim zdaniem prawidłowe rozwiązania są 2:

  1. Zamknąć każde rand() w sekcji krytycznej, dla wykluczania równoczesnego dostępu do generatora.
  2. Zamiast generatora ze standardowej biblioteki, wziąć generator z boost-a (np rand48), każdy wątek miałby własny generator z własną zmienną ziarna bo wszystko zamknięte jest w obiekcie pewnej klasy. Oczywiście należy sprytnie nadać wartości początkowe ziarna by były różne dla każdego wątku. (to zalecam)

Jeśli to co napisałem nie ma rąk i nóg to szybko wyprowadźcie mnie z błędu.

0

hm.. nie lubie frazy 'ubzdurac sobie'. odpalales kod ktory zamiescilem i probowales odpalac go we wszystkich opisanych przeze mnie wersjach?

przyznam ze myslalem dokladnie tak samo jak Ty, ale postanowilem to sprawdzic i tak sie zaczal ten caly watek.. ponizej przykladowa wersja w/w kodu

struct letter
{
	...
	void print_thread()
	{	...

		//srand((int)std::time(0));   //wykomentowuje

		unsigned long x = 3;
		do
		{	
			xtime t;
			xtime_get(&t, TIME_UTC);
			t.nsec += 1;
			thread::sleep(t); 
			mutex::scoped_lock lock(out.guard);         //<--- patrz tutaj, wiesz co to jest. i to bylo od samego poczatku tutaj
			out.ostr << chr << ':' << rand() << endl;   //<-- a tutaj jest RAND
		}while(--x);
		...
	}
};

int main()
{
	...	
	srand((int)std::time(0));  //odkomentowuje

	string const s = "abc";   //i ograniczam do trzech
	...
}

wynik pod msvc2005, x86, winxp:

a:41
b:41
c:41
b:18467
a:18467
c:18467
b:6334
a:6334
c:6334
b:!
a:!
c:!

naprawde sadzisz ze jest konflikt watkow gdzies w srand albo w rand? bo ja takiej mozliwosci nie widze. a wszystkie watki wypisaly dokladnie to samo. dla mnie - to oznacza, ze maja ten sam seed (edit: poczatkowy) i ze maja osobne (edit: tzn. niezalezne) stany RNG. przekonaj mnie ze sie myle, moglem cos przeoczyc

0

Na linuksie bez czekania też się czasem wywala.

Po dodaniu

~letter(){
		boost::mutex::scoped_lock lock(out.guard);
		out.ostr << chr<< ": adres chr " <<(unsigned long)  &chr <<std:: endl;  
	
	};

widać

(..)
N: adres chr 134549704
O:1215972361
O:1454027257
O:304647999
O:!
O: adres chr 134549904
�:730437149
�:955633738
�:1507907832
�:!
�: adres chr 134549904

  • glibc detected *** ./a.out: double free or corruption (fasttop): 0x08051190 ***

że raz adres chr jest taki sam, czasem inny.

0

oo dzieki za info! sprobowalbys wykomentowac delete this? na windozie wtedy sie przestaje wywalac.. osobiscie nigdy nie uzywam delete this'a, ma to dla mnie 'wewnetrzne fuj' :) ale tutaj, autentycznie nie moge dojsc czemu delete this szkodzi

0

1.Z "delete this", nawet, jak się nie wywala to nie działa dobrze.

dla:
string const s = "0123456789ABCDEFGIJKLMNO";

O:1851321447
O:153412249
O:925038440
O:!
O: adres chr 134550000
(...)
X:58220799
X:724725102
X:663807060
X:!
X: adres chr 134550000

No, a przecież "X" nie było. Adresy są te same, a tak nie powinno być.(chyba, że wątek poprzedni zwolnił pamięć i nowy dostał dokładnie tą samą)

  1. Gdy zastąpię print_thread() czymś takim:
void print_thread()
{      
  delete this;
}

to też się czasem wywala.

  1. Po usunięciu "delete this;" działa.(sprawdzałem 1000 razy dla twojego programu i kilkudziesięciu wątków)
0

tak.. z tymi adresami zgada sie, zlapalem to rownolegle z Toba. powod tkwi tutaj:

w wersji rozwinietej:

        string::const_iterator it = s.begin(), end = s.end();
        while(it!=end)
        {        tmp = new letter(*it, go);
                threads.add_thread(new thread(bind(&letter::print_thread, var(tmp))));  //var(tmp)!!!!
                xtime_get(&t, TIME_UTC);
                t.nsec+=100 * 000 * 1000;
                thread::sleep(t);
                ++it;
        }

thread dostaje lambde (1) jako procedure watku. ona, po odpaleniu pobiera obiekt z wywolania lambdy (2) czyli var(tmp). ale lambda (1) zostaje odpalona dopiero przez watek! a wiec wynik bind(&letter::print_thread, var(tmp)) dopiero przez watek jest odpalany totez var(tmp) rowniez.. i stad raz-na-jakis-czas okazuje sie ze dwa watki dostaly ten sam this!

prosta zmiana na przekazywanie przez kopie wartosci:

                threads.add_thread(new thread(bind(&letter::print_thread, tmp))); 

i wszystko pieknie chodzi.

a teraz w wersji zwinietej.. problem jest ten sam: bledne uzycie VAR. powinienem byl wymusic skopiowanie tego wskaznika do funktora watku.. to sie teraz glowie jak. ciekawe czy w ogole sie da?

1 użytkowników online, w tym zalogowanych: 0, gości: 1