🚀 Nuova versione beta disponibile! Feedback o problemi? Contattaci

Esercizi Condition Variables C++

Codegrind Team•Jul 12 2024

Ecco degli esercizi semplici con soluzione per praticare le basi delle condition variables nel threading in C++.

Esercizio 1

Utilizzare una condition variable per sincronizzare l'accesso a una risorsa condivisa tra due thread.
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>

std::mutex mtx;
std::condition_variable cv;
bool pronto = false;
int dato = 0;

void produttore() {
    std::unique_lock<std::mutex> lock(mtx);
    dato = 42;
    pronto = true;
    cv.notify_one();
}

void consumatore() {
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock, []{ return pronto; });
    std::cout << "Dato ricevuto: " << dato << std::endl;
}

int main() {
    std::thread t1(produttore);
    std::thread t2(consumatore);

    t1.join();
    t2.join();

    return 0;
}

Esercizio 2

Implementare una coda thread-safe utilizzando condition variables.
#include <iostream>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>

template <typename T>
class CodaThreadSafe {
private:
    std::queue<T> coda;
    std::mutex mtx;
    std::condition_variable cv;
public:
    void push(T val) {
        std::unique_lock<std::mutex> lock(mtx);
        coda.push(val);
        cv.notify_one();
    }

    T pop() {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, [this]{ return !coda.empty(); });
        T val = coda.front();
        coda.pop();
        return val;
    }
};

CodaThreadSafe<int> coda;

void produttore() {
    for (int i = 0; i < 5; ++i) {
        coda.push(i);
        std::cout << "Prodotto: " << i << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}

void consumatore() {
    for (int i = 0; i < 5; ++i) {
        int val = coda.pop();
        std::cout << "Consumato: " << val << std::endl;
    }
}

int main() {
    std::thread t1(produttore);
    std::thread t2(consumatore);

    t1.join();
    t2.join();

    return 0;
}

Esercizio 3

Utilizzare una condition variable per implementare un semplice semaforo.
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>

class Semaforo {
private:
    std::mutex mtx;
    std::condition_variable cv;
    int contatore;
public:
    Semaforo(int valore_iniziale) : contatore(valore_iniziale) {}

    void wait() {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, [this] { return contatore > 0; });
        --contatore;
    }

    void signal() {
        std::unique_lock<std::mutex> lock(mtx);
        ++contatore;
        cv.notify_one();
    }
};

Semaforo semaforo(1);

void funzione1() {
    semaforo.wait();
    std::cout << "Funzione 1 in esecuzione" << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));
    semaforo.signal();
}

void funzione2() {
    semaforo.wait();
    std::cout << "Funzione 2 in esecuzione" << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));
    semaforo.signal();
}

int main() {
    std::thread t1(funzione1);
    std::thread t2(funzione2);

    t1.join();
    t2.join();

    return 0;
}

Esercizio 4

Utilizzare una condition variable per implementare un ciclo produttore-consumatore con più produttori e consumatori.
#include <iostream>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>

std::queue<int> coda;
std::mutex mtx;
std::condition_variable cv;
const int MAX_CODA = 10;
bool done = false;

void produttore(int id) {
    for (int i = 0; i < 10; ++i) {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, []{ return coda.size() < MAX_CODA; });
        coda.push(i);
        std::cout << "Produttore " << id << " ha prodotto: " << i << std::endl;
        cv.notify_all();
    }
}

void consumatore(int id) {
    while (true) {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, []{ return !coda.empty() || done; });
        if (done && coda.empty()) break;
        int val = coda.front();
        coda.pop();
        std::cout << "Consumatore " << id << " ha consumato: " << val << std::endl;
        cv.notify_all();
    }
}

int main() {
    std::thread produttori[3], consumatori[3];
    for (int i = 0; i < 3; ++i) {
        produttori[i] = std::thread(produttore, i+1);
        consumatori[i] = std::thread(consumatore, i+1);
    }

    for (int i = 0; i < 3; ++i) {
        produttori[i].join();
    }

    {
        std::unique_lock<std::mutex> lock(mtx);
        done = true;
        cv.notify_all();
    }

    for (int i = 0; i < 3; ++i) {
        consumatori[i].join();
    }

    return 0;
}

Esercizio 5

Utilizzare una condition variable per implementare un sistema di lettori-scrittori.
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>

std::mutex mtx;
std::condition_variable cv;
int lettori = 0;
bool scrittura_in_corso = false;

void lettore(int id) {
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock, []{ return !scrittura_in_corso; });
    ++lettori;
    lock.unlock();

    std::cout << "Lettore " << id << " sta leggendo." << std::endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(500));

    lock.lock();
    --lettori;
    if (lettori == 0) {
        cv.notify_all();
    }
    lock.unlock();
}

void scrittore(int id) {
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock, []{ return !scrittura_in_corso && lettori == 0; });
    scrittura_in_corso = true;
    lock.unlock();

    std::cout << "Scrittore " << id << " sta scrivendo." << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));

    lock.lock();
    scrittura_in_corso = false;
    cv.notify_all();
    lock.unlock();
}

int main() {
    std::thread lettori[3], scrittori[2];

    for (int i = 0; i < 3; ++i) {
        lettori[i] = std::thread(lettore, i+1);
    }
    for (int i = 0; i < 2; ++i) {
        scrittori[i] = std::thread(scrittore, i+1);
    }

    for (int i = 0; i < 3; ++i) {
        lettori[i].join();
    }
    for (int i = 0; i < 2; ++i) {
        scrittori[i].join();
    }

    return 0;
}
``

`

</div>
</details>

### Esercizio 6

<details class="mb-10">
<summary class="cursor-pointer">Utilizzare una condition variable per sincronizzare l'accesso a un contatore condiviso tra più thread.</summary>
<div class="py-5">

```cpp
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>

int contatore = 0;
std::mutex mtx;
std::condition_variable cv;

void incrementa(int id) {
    for (int i = 0; i < 5; ++i) {
        std::unique_lock<std::mutex> lock(mtx);
        ++contatore;
        std::cout << "Thread " << id << " incrementa a " << contatore << std::endl;
        cv.notify_all();
        cv.wait(lock, []{ return contatore % 5 == 0; });
    }
}

void stampa() {
    while (true) {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, []{ return contatore % 5 == 0 && contatore > 0; });
        std::cout << "Contatore raggiunge multiplo di 5: " << contatore << std::endl;
        if (contatore >= 20) break;
    }
}

int main() {
    std::thread t1(incrementa, 1);
    std::thread t2(incrementa, 2);
    std::thread t3(stampa);

    t1.join();
    t2.join();
    t3.join();

    return 0;
}

Esercizio 7

Utilizzare una condition variable per implementare una barriera di sincronizzazione.
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>

const int NUM_THREAD = 5;
int count = 0;
std::mutex mtx;
std::condition_variable cv;

void threadFunzione(int id) {
    std::cout << "Thread " << id << " pronto\n";

    std::unique_lock<std::mutex> lock(mtx);
    ++count;
    if (count == NUM_THREAD) {
        cv.notify_all();
    } else {
        cv.wait(lock, []{ return count == NUM_THREAD; });
    }

    std::cout << "Thread " << id << " inizia\n";
}

int main() {
    std::thread threads[NUM_THREAD];

    for (int i = 0; i < NUM_THREAD; ++i) {
        threads[i] = std::thread(threadFunzione, i + 1);
    }

    for (int i = 0; i < NUM_THREAD; ++i) {
        threads[i].join();
    }

    return 0;
}

Esercizio 8

Utilizzare una condition variable per implementare un sistema di lettura e scrittura con priorità ai lettori.
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>

std::mutex mtx;
std::condition_variable cv;
int lettori = 0;
bool scrittura_in_corso = false;

void lettore(int id) {
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock, []{ return !scrittura_in_corso; });
    ++lettori;
    lock.unlock();

    std::cout << "Lettore " << id << " sta leggendo.\n";
    std::this_thread::sleep_for(std::chrono::milliseconds(500));

    lock.lock();
    --lettori;
    if (lettori == 0) {
        cv.notify_all();
    }
    lock.unlock();
}

void scrittore(int id) {
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock, []{ return !scrittura_in_corso && lettori == 0; });
    scrittura_in_corso = true;
    lock.unlock();

    std::cout << "Scrittore " << id << " sta scrivendo.\n";
    std::this_thread::sleep_for(std::chrono::seconds(1));

    lock.lock();
    scrittura_in_corso = false;
    cv.notify_all();
    lock.unlock();
}

int main() {
    std::thread lettori[3], scrittori[2];

    for (int i = 0; i < 3; ++i) {
        lettori[i] = std::thread(lettore, i+1);
    }
    for (int i = 0; i < 2; ++i) {
        scrittori[i] = std::thread(scrittore, i+1);
    }

    for (int i = 0; i < 3; ++i) {
        lettori[i].join();
    }
    for (int i = 0; i < 2; ++i) {
        scrittori[i].join();
    }

    return 0;
}