PDA

Prikaži potpunu verziju : [C++] VS11 DP i C++11 multithreading


Belphegor
30.3.2012, 14:43
Prvo da kazem da nikad nisam radio sa tredovima tako da mi nije poznato kako tu stvari funkcionisu.
Tek sam poceo da citam tutorial sa ove stranice (http://www.devx.com/SpecialReports/Article/38883/0/page/1) a istovremeno polako i isprobavam u VS-u pa sam naleteo na "snag", evo koda:


#include <iostream>
#include <limits>
#include <thread>
#include <mutex>

std::mutex m;

class MyWorker1
{
private:
int data;
public:
MyWorker1() : data(7) {}

void operator () (const std::string& str)
{
std::lock_guard<std::mutex> lock(m);
std::cout << "====== MyWorker1 ======" << std::endl;
std::cout << "data = " << data << std::endl;
std::cout << "str = " << str.c_str() << std::endl;
}
};

class MyWorker2
{
private:
int data;
public:
MyWorker2() : data(8) {}

void operator () (double d)
{
std::lock_guard<std::mutex> lock(m);
std::cout << "====== MyWorker2 ======" << std::endl;
std::cout << "data = " << data << std::endl;
std::cout << "d = " << d << std::endl;
}
};

int main()
{
{
MyWorker1 worker1;
std::string str = "Test";
//std::thread my_thread1(std::ref(worker1), std::ref(str));
std::thread my_thread1(worker1, str);

MyWorker2 worker2;
double d = 3.14;
//std::thread my_thread2(std::ref(worker2), d);
std::thread my_thread2(worker2, d);

//my_thread1.join();
//my_thread2.join();

//my_thread1.detach();
//my_thread2.detach();
}

std::cout << "Press enter to exit...";
std::cin.ignore( std::numeric_limits< std::streamsize >::max(), '\n' );
return EXIT_SUCCESS;
}


Ovo me doceka sa error-om:

http://www.dodaj.rs/f/b/11X/4L7YRw2U/error.jpg

1. Da li uopste ima uticaja "vestacki scope" ako kreiram tred u njemu?

{
std::thread th(foo);
}

Po nekoj mojoj logici mislio sam da ce na kraju bloka da se 'th' tred detacovati/samoubiti.

2. Ako odkomentarisem detach() onda mi "Press enter to exit..." text dodje umixovan sa ostalim od workera skroz pri vrhu, a ako odkomentarisem join() a komentarisem detach() onda imam ocekivan ispis. :confused:

EclipsE
30.3.2012, 15:22
1. Well, u boost-u destruktor thread-a sam poziva detach() što zauzvrat oslobađa thread, tako da objekat će biti uništen ali će se thread ipak završiti do kraja.

{
std::thread th(foo);
}

// th ne postoji, ali foo se i dalje pokreće!

Ne postoji način da "ubiješ" thread, jedino možeš da sačekaš da završiš ili da se program završi, što automatski ubija thread.

2. Well potvrđuje sve što sam napisao :D
Posle detach, threadovi su slobodni. Main thread nastavi, ali tako nastave i druga dva thread-a, samo što se main thread blokira na:

std::cin.ignore( std::numeric_limits< std::streamsize >::max(), '\n' );

Ako prvo joinuješ threadove, main thread se blokira dok drugi ne završe i onda dobiješ rezultat koji si hteo.

EDIT:

Čitah malo o ovome, i ispade da u C++0x destruktor ne poziva detach, već korisnik mora sam da ga pozove... Možda si zato dobio onaj error?

Belphegor
31.3.2012, 1:14
Nova pitanja. :D

Primer koda sa one stranice, cekanje na event:

std::mutex m;
std::condition_variable cond;
bool data_ready;

void process_data();

// thread 1
void foo()
{
std::unique_lock<std::mutex> lk(m);
while(!data_ready)
{
cond.wait(lk);
}
process_data();
}
...
// thread 2 podesava data_ready
void set_data_ready()
{
std::lock_guard<std::mutex> lk(m);
data_ready = true;
cond.notify_one();
}Posto sam citao neki text da kompajler moze da radi "cudne" stvari tokom optimzacije, a onaj tutorijal ne spominje nista oko toga.
Da li je u ovom slucaju neophodno da data_ready bude voilatile, posto vidim u nekim primerima da kompajler moze da izmeni instrukcije?

while(!data_ready)
{
...
}
pa zbog optimizacije bude:

while(true)
{
...
}
i nikad ne dodje do process_data().

zokocx
31.3.2012, 1:59
Kako ja znam trebalo bi, jer voilatile oznacava da ce promenljiva imati fixno mesto u memoriju i mogu da je menjaju drugi threadovi i itd.
Jer ako ovako pustis sto si napisao, moze da se desi da set_data_ready() namesti true ko zna na sta.

Anyway i C# ima tu rezervisanu rec voilatile (isto sluzi) i uopste mi se ne svidja, cak mislim da sam citao da za takvo crossthread modifikovanje podatake je dosta losa praksa koristit voilatile promenljivu, jedino za readonly je OK.

M.Silenus
31.3.2012, 11:45
Fino objašnjenje zašto treba koristiti volatile : volatile: The Multithreaded Programmer's Best Friend (http://drdobbs.com/cpp/184403766).

C++11 standardna biblioteka nudi std::atomic (http://www.stdthread.co.uk/doc/headers/atomic/atomic.html).
Na primer, umesto
volatile int x;
pisao bi:
std::atomic<int> x;
i onda bi koristio store i load za pisanje i čitanje:

x.store(5);
int y = x.load();


load i store su, je li, atomične operacije u ovom kontekstu.

Plus, korišćenjem std::atomic-a kažeš kompajleru koja je namena promenljive.

Dalje, za komunikaciju između niti možeš da koristiš std::future i std:: promise.

Preporučio bih ti da pogledaš sledeće tutorijale : C++11 Concurrency Series by Bartosz Milewski (http://www.corensic.com/Learn/Resources/ConcurrencyTutorialPartOne.aspx).

Belphegor
31.3.2012, 13:06
Gledam sad video sto si postavio link od Bartosz Milewsk-og, koliko vidim izgleda da je cout thread-safe posto nije koristio "lock" mehanizme, a ispisan text nije "zbrckan", ili to zavisi od iplementacije standardne biblioteke posto je on koristio just::thread na VS2010? Htedoh reci, da li vazi isto za std koji ide uz VS2011 Developer preview posto ne mogu da nadjem konkretan odgovor koristeci google.

EDIT: Evo gledam sad pred kraj, kad povezuje vise << (shift operatora), pocinje "zbrka". :D
Ali izgleda da je ovako samostalno std::cout << "neki text"; "atomicna operacija"? Odnosno ne moze da upadne nista izmedju?

M.Silenus
31.3.2012, 17:25
Iz C++ standarda:


27.4.1:
...
4. Concurrent access to a synchronized standard iostream object's formatted or unformatted input and output functions or a standard C stream by multiple threads shall not result in a data race.
Note: Users must still synchronize concurrent use of these objects and streams by multiple threads if they wish to avoid interleaved characters.


Znači, jedino što imaš je garanciju da upis u ostream objekat neće da ti blokira aplikaciju. Za ispravan ispis i dalje moraš da koristiš mutex.

On related note, možda bi bilo korisno da napraviš wrapper oko ostream klase koja nudi atomični write. Evo primera:


#include <ostream>
#include <thread>
#include <mutex>

class thread_safe_ostream
{
std::ostream& _stream;
std::mutex _mutex;
public:
explicit thread_safe_ostream(std::ostream& str = std::cout)
:_stream(str)
{}

template<typename ... Args>
void writeln(Args const& ... args)
{
std::lock_guard<std::mutex> lck(_mutex);
writeln_impl(args...);
}

private:
void writeln_impl()
{
_stream << "\n";
}

template<typename T>
void writeln_impl(T const& t)
{
_stream << t << "\n";
}

template<typename H, typename ... T>
void writeln_impl(H const& h, T const& ... t)
{
_stream << h;
writeln_impl(t...);
}
};


int main()
{
thread_safe_ostream out;
out.writeln("svet", " ", "kompjutera ", 42);
return 0;
}



A evo i verzije bez varijadika (nije moćna kao verzija sa varijadikama ali je upotrebljiva - VS11 ne podržava varijadike za razliku od GCC-a):


#include <ostream>
#include <thread>
#include <mutex>

class thread_safe_ostream
{
std::ostream& _stream;
std::mutex _mutex;
public:
explicit thread_safe_ostream(std::ostream& str = std::cout)
:_stream(str)
{}

void writeln()
{
std::lock_guard<std::mutex> lck(_mutex);
_stream << "\n";
}

template<typename T1>
void writeln(T1 const& t1)
{
std::lock_guard<std::mutex> lck(_mutex);
_stream << t1 << "\n";
}

template<typename T1, typename T2>
void writeln(T1 const& t1, T2 const& t2)
{
std::lock_guard<std::mutex> lck(_mutex);
_stream << t1 << t2 << "\n";
}

template<typename T1, typename T2, typename T3>
void writeln(T1 const& t1, T2 const& t2, T3 const& t3)
{
std::lock_guard<std::mutex> lck(_mutex);
_stream << t1 << t2 << t3 <<"\n";
}

// dodati još po potrebi
};


int main()
{
thread_safe_ostream out;
out.writeln("svet", " ", "kompjutera ", 42);
return 0;
}


U oba slučaja će ispis biti:


svet kompjutera 42


Koristim referencu na std:: ostream objekat jer dotični nije copyable!

Napomena: MinGW-GCC u trenutnoj verziji (gcc 4.6.2) ne podržava std::thread. Umesto toga koristiti boost::thread. Isti je interfejs, samo umesto std stavljati boost :)

Belphegor
31.3.2012, 17:57
Khm. :D

//explicit thread_safe_ostream(std::ostream& = std::cin)
explicit thread_safe_ostream(std::ostream& = std::cout)
...


BTW, lakse mi ovako:

class thread_safe_ostream
{
std::ostream& _stream;
std::mutex _mutex;
public:
explicit thread_safe_ostream(std::ostream& str = std::cout)
:_stream(str)
{}

template< typename T >
std::ostream& operator << (T t)
{
std::lock_guard<std::mutex> lck(_mutex);
return _stream << t;
}
};


int main()
{
thread_safe_ostream out;
out << "svet" << ' ' << "kompjutera " << 42 << std::endl;
return 0;
}



Hvala.

M.Silenus
31.3.2012, 18:19
@Belphegor Ideja iza mog writeln-a je da je kompletan ispis atomičan.

Razmotri 2 niti koje imaju ovaj poziv:


out << "svet" << ' ' << "kompjutera " << 42 << std::endl;


Jedan od mogućih izlaza bi bio:


svet svet kompjutera 42
kompjutera 42


Ovo je prosto zato što gore imaš 5 poziva operatoru <<, od kojih je svaki atomičan, ali nisu svih 5 zajedno atomični.
Ovo je jedan od razloga zašto, na primer, D programski jezik nudi writeln funkciju koja je atomična i koja nudi proizvoljan broj argumenata. BTW. nije ista kao ova moja.

U slučaju mog writeln-a:


writeln("svet", " ", "kompjutera ", 42);


Izlaz je:


svet kompjutera 42
svet kompjutera 42

Belphegor
31.3.2012, 18:20
Da, da, da... :facepalm

Belphegor
1.4.2012, 22:00
Vracam se na VS2010, pa sam skinuo Boost biblioteku (1.47) da probam. :D

Citajuci neki text koliko sam razumeo kreiranje niti ume da bude sporo pa me interesuje odlozeno startovanje "niti", gde bih kreirao nit u nekom inicijalnom delu programa da ne bih imao "stucanja" kasnije, a izgleda da ne postoje nativne metode za to?

1. Treba mi std::atomic ekvivalent u Boost-u, koliko vidim ne ide uz ovaj paket?
Pa sam nasao odavde neki dodatak (http://www.chaoticmind.net/%7Ehcb/projects/boost.atomic/). Jel mora posebno da se doda ili sam prevideo nesto?

2. Treba mi jedna nezavisna nit da radi koliko i sam program pa sam mislio da blokiram nit dok ne bude sve spremno, nesto ovako (lupam):


boost::atomic_bool thWait;
thWait.store(true);
boost::condition_variable cv;
boost::mutex m;

void foo ()
{
// odlaganje
boost::unique_lock<boost::mutex> lk(m);
cv.wait(lk, [&thWait] { return thWait; });

// rad
while(1);
...
}

void init()
{
boost::thread th(&foo);
...// ovde dosta toga
...

// sad se startuj
boost::lock_guard<boost::mutex> lk(m);
thWait=false;
cv.notify_one();
}
Neke bolje ideje?

EclipsE
1.4.2012, 22:16
How much overhead is there when creating a thread? (http://stackoverflow.com/questions/3929774/how-much-overhead-is-there-when-creating-a-thread)

Sada radim projekat iz operativnih sistema i imamo cilj da simuliramo kernel i pravimo svoje threadove. Ne znam sta boost radi, ali princip:

class MyThread : public Thread {
public:
...
virtual void run() {
// do something
}
};

...

MyThread myThread = new MyThread();
myThread->start();


Threadovi su Java-style. U konstruktoru Thread-a se vrsi samo registrovanje thread-a kernel-u i kreiranje konteksta tako sto se alocira neki stack na heap-u, i onda se u taj stack pushuju pocetne vrednosti registara i taj kod je bukvalno sve sto je potrebno za kreiranje thread-a.

Operativni sistem naravno verovatno radi jos neke stvari, ali to je ceo koncept. Cak i u post-u na stackoverflow-u mozes da procitas:

In terms of overhead, thread creation/destruction, especially on Windows, is fairly expensive. Somewhere on the order of tens of microseconds, to be specific.

Meni ovo uopste nije expensive... Naravno, ako ces da kreiras 1000 threadova zaredom, onda jeste :p

A za 2., zasto jednostavno ne napravis nov thread na kraju programa posto do tad ga odlazes? :D

Belphegor
1.4.2012, 22:26
Naravno, ako ces da kreiras 1000 threadova zaredom, onda jeste...


Pa sad necu da kreiram hiljade tredova i sta ti ja znam. :D
Tek sam poceo i hteo sam da probam nesto (vezbam) a da i vidim kakve sve opcije imam.


zasto jednostavno ne napravis nov thread na kraju programa posto do tad ga odlazes...

Bukvalisto, :a_bleh: :D
pa samo sam dao glup primer kako mislim da treba da izgleda, a ne zelim da me ogranicava kad cu da ga napravim, nebitno koliko je sporo kreiranje.

EclipsE
1.4.2012, 22:45
Nadjoh ovo:

#include <boost/thread/condition_variable.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/thread/lock_types.hpp>

class semaphore
{
//The current semaphore count.
unsigned int count_;

//mutex_ protects count_.
//Any code that reads or writes the count_ data must hold a lock on
//the mutex.
boost::mutex mutex_;

//Code that increments count_ must notify the condition variable.
boost::condition_variable condition_;

public:
explicit semaphore(unsigned int initial_count)
: count_(initial_count),
mutex_(),
condition_()
{
}

unsigned int get_count() //for debugging/testing only
{
//The "lock" object locks the mutex when it's constructed,
//and unlocks it when it's destroyed.
boost::unique_lock<boost::mutex> lock(mutex_);
return count_;
}

void signal() //called "release" in Java
{
boost::unique_lock<boost::mutex> lock(mutex_);

++count_;

//Wake up any waiting threads.
//Always do this, even if count_ wasn't 0 on entry.
//Otherwise, we might not wake up enough waiting threads if we
//get a number of signal() calls in a row.
condition_.notify_one();
}

void wait() //called "acquire" in Java
{
boost::unique_lock<boost::mutex> lock(mutex_);
while (count_ == 0)
{
condition_.wait(lock);
}
--count_;
}

};

Onda mozes:

semaphore sem = semaphore(0);

void foo()
{
// odlaganje
sem.wait();
}

void init()
{
boost::thread th(&foo);

// notify now
sem.signal();
}

Cudi me da boost nema semaphore... bar ih ja nisam pronasao. BTW nisam siguran da ovo radi, na SO (http://stackoverflow.com/questions/3928853/how-can-i-achieve-something-similar-to-a-semaphore-using-boost-in-c) kazu da radi :D

M.Silenus
2.4.2012, 9:22
Threadovi i mutexi su najniži ne-mašinski nivo apstrakcije za konkurentno programiranje. Tako ih mašina + OS vidi, minus platform specifics.

Generalno "pravilo" (više vodilja) je: Izbegavaj pravljenje velikog broja niti.

Umesto toga, pokreni nekoliko niti na početku aplikacije (videti thread-pool pattern (http://en.wikipedia.org/wiki/Thread_pool_pattern)), i šalji im poslove - ovo je takozvani task-based parallelism. Ovim ne plaćaš pokretanje niti, već samo komunikaciju (mnoooogoooo jeftinije).

Zato neki jezici ne koriste termin thread, nego coroutine, green thread, goroutine... što su sve fensi nazivi za task-ove.

Preporuka: koristi biblioteku koja eksplicitno podržava task-based parallelism (na primer: Intel Threading Building Blocks (http://threadingbuildingblocks.org/) ili Microsoft Parallel Patterns Library (http://msdn.microsoft.com/en-us/library/dd492418.aspx).

Naravno, nemoj da se bakćeš ovim dok učiš osnovne niti.

Belphegor
2.4.2012, 13:27
Hvala za kljucnu rec "thread pool", tako da znam sta da trazim. :D
Izgleda da uz Boost paket ne ide threadpool pa sam nasao dodatak ovde (http://threadpool.sourceforge.net/index.html). Da li sam pogresio? Mislim da dzaba ne izmisljam vodu kad je vec neko pravio. :D

M.Silenus
2.4.2012, 17:53
To za izmišljanje tople vode stoji :D

Nažalost, nemam nikakvih iskustava sa threadpool bibliotekom (od task-based parallelism biblioteka imam nešto iskustva sa TBB - i odlična je).

U okviru Boost Sandbox-a (http://www.boost.org/community/sandbox.html) (biblioteke koje se razmatraju za ubacivanje u Boost) sam video Boost.Task koja, izgleda, radi istu stvar. Pogledaj, možda ti više odgovara.
Napomena, samo zato što je u Sandbox-u ne znači da će biti uključena u Boost.

Pretpostavljam da ti je jedino rešenje da eksperimentišeš, pa da sam odlučiš šta ti odgovara (slow clap za najbeskorisniji odgovor ikada).

Javi kako idu eksperimenti ;)

Geomaster
6.4.2012, 20:06
Da se nadovežem.

Ne mogu da ti dam opšti odgovor, ali znam da u Linux kernelu lightweight procesi (iliti threadovi) nemaju nikakve informacije uz sebe osim konteksta, što će reći samo stanje registara. Kreiranje sâmog thread-a je jako jeftina operacija s obzirom da sve što treba uraditi je alokacija steka za novi thread (ovo je najskuplja operacija u procesu, i performanse zavisi od implementacije dinamičkog alokatora u kernelu) a zatim guranje svih relevantnih registara (pri čemu može da se uštedi na FPU registrima tako da se oni učitavaju tek kada se koriste) u neku kernelovu strukturu podataka. Switching između threadova je takođe jako lightweight operacije jer zahteva samo snimanje prethodnog konteksta i učitavanje novog. Međutim, alokacija zna da bude bottleneck u ovakvim scenarijima tako da bih ti ja preporučio da, ako ti treba mnogo threadova pokrenutih u malim vremenskim razmacima, koristiš neki thread pool.

EDIT: Jesam duduk, EclipsE je sve ovo već napisao -.-