2015-10-04 12 views
5

Rozważmy prosty przykład zbieżność:Zamówienie wątków w wykonaniu

#include <iostream>  // std::cout 
#include <thread>   // std::thread 
#include <mutex>   // std::mutex 

std::mutex mtx;   // mutex for critical section 

void print_block(int n, char c) { 
    // critical section (exclusive access to std::cout signaled by locking mtx): 
    mtx.lock(); 
    for (int i = 0; i<n; ++i) { std::cout << c; } 
    std::cout << '\n'; 
    mtx.unlock(); 
} 

int main() 
{ 
    std::thread th1(print_block, 50, '*'); 
    std::thread th2(print_block, 50, '$'); 

    th1.join(); 
    th2.join(); 

    return 0; 
} 

Czy zawsze gwarantowane że th1 będzie pierwszy wątek do wykonania pętli for?

znaczenie, kiedy to zrobić:

th1.join(); 
th2.join(); 

Wtedy możemy być absolutnie pewni, że th1 będzie pierwszy, a następnie th2 stracony?

+1

When zajmowanie się programowaniem współbieżnym, nigdy nie wysuwaj żadnej hipotezy o kolejności wykonywania. Przeciwnie, zakładaj, że wszystkie sytuacje są możliwe. Dzięki temu Twój kod jest bardziej niezawodny i paradoksalnie prostszy. –

+0

[Używasz 'std :: mutex' wrong] (http://kayari.org/cxx/antipatterns.html#locking-mutex), nie powinieneś nigdy wywoływać' std :: mutex :: lock() 'i 'std :: mutex :: unlock()'. Zamiast tego stwórz 'std :: lock_guard ' na początku 'print_block()' i pozwól mu zablokować i odblokować muteks dla ciebie. –

Odpowiedz

6

Nie, najprawdopodobniej najpierw widzisz th1, ponieważ konstrukcja gwintu dla tej zmiennej jest wykonywana jako pierwsza (a budowa nici jest droga), a zatem th2 rozpoczyna się po. To nie znaczy, że istnieje zamówienie.

Wywołanie join() nie ma nic wspólnego z tym, który wątek zostanie wykonany jako pierwszy, jest to robione podczas budowy, gdy podasz wywoływalny.

th1 może zostać skonstruowany, a następnie wstrzymany przez system operacyjny, co spowoduje, że najpierw zostanie uruchomiony th2. Nie ma porządku, jeśli go nie wdrożysz.

Rozważmy przykład, który daje znacznie bardziej sprawiedliwy początek obu nici, czasami wyjścia gwintu 1 jako pierwszą nabyć zamka, czasami wyjścia gwint 2.

przykład:

#include <iostream>   // std::cout 
#include <string>   // std::string 
#include <unordered_map> // std::unordered_map<K, V> 
#include <thread>   // std::thread 
#include <mutex>   // std::mutex 
#include <atomic>   // std::atomic<T> 

std::unordered_map<std::thread::id, std::string> thread_map; 
std::mutex mtx;   // mutex for critical section 
std::atomic<bool> go{ false }; 

void print_block(int n, char c) 
{ 
    while (!go) {} // prevent threads from executing until go is set. 
    // critical section (exclusive access to std::cout signaled by locking mtx): 
    mtx.lock(); 

    std::cout << thread_map.find(std::this_thread::get_id())->second << 
     " acquires the lock.\n"; 

    mtx.unlock(); 
} 

int main() 
{ 
    std::thread th1(print_block, 50, '*'); 
    std::thread th2(print_block, 50, '$'); 

    thread_map.emplace(std::make_pair(th1.get_id(), "Thread 1")); 
    thread_map.emplace(std::make_pair(th2.get_id(), "Thread 2")); 

    go.store(true); 

    th1.join(); 
    th2.join(); 

    return 0; 
} 
+0

Ale w moim przykładzie, dlaczego zamówienie jest zawsze takie samo? pierwsze 'th1', a następnie' th2'? Nawet gdy zamieniłem linie jedna na drugą, zamówienie pozostało takie samo. Dopiero po wymianie konstrukcji zamówienie uległo zmianie. – ron

+0

@ron Kiedy zastąpiłeś które linie ze sobą? Jeśli masz na myśli wywołania 'join()', to dlatego, że 'join()' nie ma nic wspólnego z kolejnością, w której wątki zaczynają wykonywać. Wyjaśniłem, dlaczego najpierw budowanie jednego wątku zwykle powoduje, że zaczyna się on przed drugim. –

+0

OK, zobaczę, czy mam ciebie: oba wątki docierają do pętli while ('while (! Go) {}'), a następnie wątek główny odwraca flagę 'go', i tylko wtedy istnieje warunek wyścigu do zdobyć zamek? – ron