본문 바로가기
코딩테스트

C++ ] leetCode 1195 - Fizz Buzz Multithreaded

by eteo 2024. 5. 6.

 

 

리트코드 1195번 문제

 

You have the four functions: 

  • printFizz that prints the word "fizz" to the console, 
  • printBuzz that prints the word "buzz" to the console, 
  • printFizzBuzz that prints the word "fizzbuzz" to the console, and 
  • printNumber that prints a given integer to the console. 

 

You are given an instance of the class FizzBuzz that has four functions: fizz, buzz, fizzbuzz and number. The same instance of FizzBuzz will be passed to four different threads: 

  • Thread A: calls fizz() that should output the word "fizz". 
  • Thread B: calls buzz() that should output the word "buzz". 
  • Thread C: calls fizzbuzz() that should output the word "fizzbuzz". 
  • Thread D: calls number() that should only output the integers. 

 

Modify the given class to output the series [1, 2, "fizz", 4, "buzz", ...] where the ith token (1-indexed) of the series is:

"fizzbuzz" if i is divisible by 3 and 5,

"fizz" if i is divisible by 3 and not 5,

"buzz" if i is divisible by 5 and not 3, or

i if i is not divisible by 3 or 5.

 

 

Implement the FizzBuzz class:

  • FizzBuzz(int n) Initializes the object with the number n that represents the length of the sequence that should be printed.
  • void fizz(printFizz) Calls printFizz to output "fizz".
  • void buzz(printBuzz) Calls printBuzz to output "buzz".
  • void fizzbuzz(printFizzBuzz) Calls printFizzBuzz to output "fizzbuzz".
  • void number(printNumber) Calls printnumber to output the numbers.

 

 

Example 1:

  • Input: n = 15
  • Output: [1,2,"fizz",4,"buzz","fizz",7,8,"fizz","buzz",11,"fizz",13,14,"fizzbuzz"]

 

Example 2:

  • Input: n = 5
  • Output: [1,2,"fizz",4,"buzz"]

 

Constraints:

  • 1 <= n <= 50

 

 

이 문제는 숫자 n이 주어졌을 때 1부터 n까지의 숫자에 대해 특정 조건(3의 배수이면서 5의 배수는 아닌 경우, 5의 배수이면서 3의 배수는 아닌 경우, 3의 배수이면서 5의 배수인 경우, 그 외의 경우)에 따라 다른 문자열 또는 숫자를 출력하면 되는 것인데..

 

문제의 핵심은 외부에선 서로 다른 4개의 스레드가 FizzBuzz 클래스의 인스턴스를 전달받아 4개의 멤버함수를 각각 무지성 호출한 상태이고, 내가 직접 각 멤버함수의 내부를 구현해서 멀티스레딩 환경에서도 정확한 순서와 조건에 맞게 출력하도록 하면 되는 것이다.

 

 

1. 클래스의 멤버변수로 현재 처리중인 숫자를 의미하는 a를 두고 뮤텍스 mtx와 조건변수 cv를 사용해 공유데이터인 a에 대한 접근을 동기화 한다.

 

2. 각 메서드는 무한 루프로 실행되는데 cv.wait()을 통해 해당 스레드의 처리 조건이 맞을 때까지 대기한다. 조건이 상호 배타적이므로 넷 중 한 스레드만이 깨어나서 작업을 처리한다.

 

3. 조건에 맞는 경우 해당 문자열 또는 숫자를 출력하는 함수를 실행하고 a를 증가시킨 뒤 cv.notify_all()을 통해 대기중인 모든 스레드를 깨워 상태를 다시 평가하도록 한다.

 

4. 마지막 숫자 n에 대한 출력 이후 깨어난 모든 스레드들은 a > n 임을 평가하고 루프를 종료한다.

 

 

 

 

class FizzBuzz {
private:
    int n;
    int a;
    mutex mtx;
    condition_variable cv;

public:
    FizzBuzz(int n) {
        this->n = n;
        this->a = 1;
    }

    // printFizz() outputs "fizz".
    void fizz(function<void()> printFizz) {
        while(true) {
            unique_lock<mutex> lck(mtx);
            cv.wait(lck, [&] { return ((a % 3 == 0) && (a % 5 != 0)) || (a > n);});
            if(a > n) break;
            printFizz();
            a++;
            cv.notify_all();
        }
    }

    // printBuzz() outputs "buzz".
    void buzz(function<void()> printBuzz) {
        while(true) {
            unique_lock<mutex> lck(mtx);
            cv.wait(lck, [&] { return ((a % 3 != 0) && (a % 5 == 0)) || (a > n);});
            if(a > n) break;
            printBuzz();
            a++;
            cv.notify_all();
        }
    }

    // printFizzBuzz() outputs "fizzbuzz".
	void fizzbuzz(function<void()> printFizzBuzz) {
        while(true) {
            unique_lock<mutex> lck(mtx);
            cv.wait(lck, [&] { return ((a % 3 == 0) && (a % 5 == 0)) || (a > n);});
            if(a > n) break;
            printFizzBuzz();
            a++;
            cv.notify_all();
        }
    }

    // printNumber(x) outputs "x", where x is an integer.
    void number(function<void(int)> printNumber) {
        while(true) {
            unique_lock<mutex> lck(mtx);
            cv.wait(lck, [&] { return ((a % 3 != 0) && (a % 5 != 0)) || (a > n);});
            if(a > n) break;
            printNumber(a);
            a++;
            cv.notify_all();
        }
    }
};

 

 

 

 

 

 

 

OnlineGDB로 테스트 해본 것

#include <iostream>
#include <thread>
#include <functional>
#include <mutex>
#include <condition_variable>

using namespace std;

class FizzBuzz {
private:
    int n;
    int a;
    mutex mtx;
    condition_variable cv;

public:
    FizzBuzz(int n) {
        this->n = n;
        this->a = 1;
    }

    // printFizz() outputs "fizz".
    void fizz(function<void()> printFizz) {
        while(true) {
            unique_lock<mutex> lck(mtx);
            cv.wait(lck, [&] { return ((a % 3 == 0) && (a % 5 != 0)) || (a > n);});
            if(a > n) break;
            printFizz();
            a++;
            cv.notify_all();
        }
    }

    // printBuzz() outputs "buzz".
    void buzz(function<void()> printBuzz) {
        while(true) {
            unique_lock<mutex> lck(mtx);
            cv.wait(lck, [&] { return ((a % 3 != 0) && (a % 5 == 0)) || (a > n);});
            if(a > n) break;
            printBuzz();
            a++;
            cv.notify_all();
        }
    }

    // printFizzBuzz() outputs "fizzbuzz".
	void fizzbuzz(function<void()> printFizzBuzz) {
        while(true) {
            unique_lock<mutex> lck(mtx);
            cv.wait(lck, [&] { return ((a % 3 == 0) && (a % 5 == 0)) || (a > n);});
            if(a > n) break;
            printFizzBuzz();
            a++;
            cv.notify_all();
        }
    }

    // printNumber(x) outputs "x", where x is an integer.
    void number(function<void(int)> printNumber) {
        while(true) {
            unique_lock<mutex> lck(mtx);
            cv.wait(lck, [&] { return ((a % 3 != 0) && (a % 5 != 0)) || (a > n);});
            if(a > n) break;
            printNumber(a);
            a++;
            cv.notify_all();
        }
    }
};

int main()
{
    int n = 15;
    FizzBuzz fizzBuzz(n);
    
    // FizzBuzz 클래스의 객체를 사용해 각 조건에 해당하는 함수를 병렬로 실행하는 쓰레드를 생성
    thread t1([&fizzBuzz] { fizzBuzz.fizz([]() { cout << "fizz" << endl; }); });
    thread t2([&fizzBuzz] { fizzBuzz.buzz([]() { cout << "buzz" << endl; }); });
    thread t3([&fizzBuzz] { fizzBuzz.fizzbuzz([]() { cout << "fizzbuzz" << endl; }); });
    thread t4([&fizzBuzz] { fizzBuzz.number([](int x) { cout << x << endl; }); });

    // 모든 쓰레드가 끝나면 메인 쓰레드를 종료
    t1.join();
    t2.join();
    t3.join();
    t4.join();

    return 0;
}