Sobes.tech
Middle

Kako radite sa višestrukim nitima u svojim projektima?

sobes.tech АИ

Одговор од АИ

U C++11 i novijim verzijama koristim <thread> za kreiranje i upravljanje nitima. Za sinhronizaciju koristim primitivne objekte iz <mutex>, <condition_variable> i <atomic>.

Osnovni pristupi:

  • std::thread: Kreiranje i pokretanje novih niti.

    #include <thread>
    #include <iostream>
    
    void worker_function() {
        std::cout << "Početak niti radnika\n";
        // ... neki rad ...
        std::cout << "Završetak niti radnika\n";
    }
    
    int main() {
        std::thread worker(worker_function);
        // ... rad u glavnoj niti ...
        worker.join(); // Čekanje da se nit radnika završi
        return 0;
    }
    
  • std::mutex: Zaštita zajedničkih podataka od istovremenog pristupa.

    #include <mutex>
    #include <thread>
    #include <vector>
    
    std::mutex data_mutex;
    std::vector<int> shared_data;
    
    void add_to_data(int value) {
        std::lock_guard<std::mutex> lock(data_mutex); // RAII zaključavanje
        shared_data.push_back(value);
    }
    
    // ... niti koje pozivaju add_to_data ...
    
  • std::lock_guard i std::unique_lock: RAII omotači za mutex-e, obezbeđujući automatsko oslobađanje zaključavanja.

    • std::lock_guard: Jednostavan blokator, koji ne dozvoljava prenos vlasništva ili odloženo zaključavanje.
    • std::unique_lock: Fleksibilniji, podržava odlagano zaključavanje, prenos vlasništva, rekurzivno zaključavanje (korišćenjem sa std::recursive_mutex).
  • std::condition_variable: Signalizacija između niti, omogućava nitima da čekaju određeno uslov.

    #include <condition_variable>
    #include <mutex>
    #include <thread>
    #include <queue>
    
    std::queue<int> data_queue;
    std::mutex queue_mutex;
    std::condition_variable data_available;
    bool stop_processing = false;
    
    void producer() {
        // ... proizvodnja podataka ...
        {
            std::lock_guard<std::mutex> lock(queue_mutex);
            data_queue.push(/* podaci */);
        }
        data_available.notify_one(); // Obaveštavanje čekajuće potrošača
    }
    
    void consumer() {
        while (!stop_processing) {
            std::unique_lock<std::mutex> lock(queue_mutex);
            data_available.wait(lock, []{ return !data_queue.empty() || stop_processing; });
    
            if (stop_processing && data_queue.empty()) {
                break;
            }
    
            int data = data_queue.front();
            data_queue.pop();
            lock.unlock(); // Otključavanje pre obrade podataka
    
            // ... obrada podataka ...
        }
    }
    
    // ... niti koje rade kao proizvođač i potrošač ...
    
  • std::atomic: Za jednostavne atomarne operacije bez korišćenja mutex-a.

    #include <atomic>
    #include <thread>
    
    std::atomic<int> counter(0);
    
    void increment_counter() {
        counter++; // Atomarsko povećanje
    }
    
    // ... niti koje pozivaju increment_counter ...
    
  • Poolovi niti: Često koristim obrazac poola niti za upravljanje resursima niti i smanjenje troškova kreiranja/brisanja.

  • std::future i std::async: Za izvršavanje asinhronih zadataka i dobijanje rezultata.

    #include <future>
    #include <iostream>
    
    int calculate_result(int input) {
        // ... složene kalkulacije ...
        return input * 2;
    }
    
    int main() {
        std::future<int> future_result = std::async(std::launch::async, calculate_result, 10);
        // ... druge radnje ...
        int result = future_result.get(); // Čekanje na rezultat
        std::cout << "Rezultat: " << result << std::endl;
        return 0;
    }
    

Prilikom rada sa višestrukim nitima, posebno obraćam pažnju na sledeće teme:

  • Utrka uslova (Race conditions): Identifikacija i sprečavanje situacija gde rezultat zavisi od nepredvidivog reda izvršenja.
  • Zaglavljivanje (Deadlock): Analiza zavisnosti između zaključavanja i primena strategija za njihovo izbegavanje.
  • Stanja aktivacije (Livelock, Starvation): Osiguranje "pravednog" rasporeda procesorskog vremena i pristupa resursima.
  • Opterećenje niti: Ujednačeno raspoređivanje rada između niti.
  • Debagovanje: Višestruki programi su teži za debagovanje zbog nepredvidivosti izvršenja.

U slučaju potrebe, za niže nivoe upravljanja ili specifične zadatke, mogu koristiti POSIX Threads (pthread) u Unix-sličnim sistemima ili Windows API za višestruko izvršavanje. Uopšteno, trudim se da koristim standardne alate C++ zbog njihove prenosivosti.