๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
C++ ๊ธฐ์ดˆ

[C++] 22. ์Šค๋ ˆ๋“œ ํ’€(Thread Pool), ์ƒ์‚ฐ์ž ์†Œ๋น„์ž ํŒจํ„ด(Producer-consumer)

by ์„œ์•„๋ž‘๐Ÿ˜ 2023. 9. 4.

 

์ง€๋‚œ ์‹œ๊ฐ„์—์„œ๋Š” ์Šค๋ ˆ๋“œ ์‚ฌ์šฉํ•  ๋•Œ ์ฃผ์˜ํ•  ์ ๊ณผ ์Šค๋ ˆ๋“œ์˜ ์‹ค์ œ ์‚ฌ์šฉ ์˜ˆ์‹œ๋ฅผ ์‚ดํŽด๋ดค์Šต๋‹ˆ๋‹ค. [C++] 21. ์Šค๋ ˆ๋“œ ๊ฒฝ์Ÿ ์ƒํƒœ(Race condition), ๋ฎคํ…์Šค(mutex), ๋ฐ๋“œ๋ฝ, std::conditional_variable, std::async

์ด๋ฒˆ ์‹œ๊ฐ„์—๋Š” ์Šค๋ ˆ๋“œ ํŒจํ‚ค์ง€์˜ ๋งˆ์ง€๋ง‰ ์‹œ๊ฐ„, ์Šค๋ ˆ๋“œ ํ’€์— ๋Œ€ํ•ด์„œ ์‚ดํŽด๋ณด์‹œ์ฃ !

 

 

 

์ƒ์‚ฐ์ž-์†Œ๋น„์ž ํŒจํ„ด(Producer-Consumer)

Producer-Consumer ํŒจํ„ด์€ ์†Œํ”„ํŠธ์›จ์–ด ๋””์ž์ธ์—์„œ ์‚ฌ์šฉ๋˜๋Š” ์ค‘์š”ํ•œ ๋””์ž์ธ ํŒจํ„ด ์ค‘ ํ•˜๋‚˜๋กœ, ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ ๊ฐ„์˜ ์ž‘์—…์„ ๋ถ„๋ฆฌํ•˜๊ณ  ์กฐ์ ˆํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ์ด ํŒจํ„ด์€ ๋‹ค์ˆ˜์˜ ์ƒ์‚ฐ์ž(Producer)์™€ ์†Œ๋น„์ž(Consumer) ๊ฐ„์˜ ํ˜‘๋ ฅ์„ ํšจ๊ณผ์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

์ƒ์‚ฐ์ž ์†Œ๋น„์ž ํŒจํ„ด์€ ๋ฐ์ดํ„ฐ์˜ ์ง„์ž…(income)๊ณผ ์ฒ˜๋ฆฌ(process)๊ฐ€ ๋ณ‘ํ–‰์ ์œผ๋กœ ์ง„ํ–‰๋  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ํ•œ ์Šค๋ ˆ๋“œ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์‚ฐ(or ์ด๋ฒคํŠธ ์ˆ˜์‹ )ํ•˜๊ณ  ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ์—์„œ ์ด ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒฝ์šฐ์— ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.


์ƒ์‚ฐ์ž-์†Œ๋น„์ž ํŒจํ„ด์˜ ๊ตฌ์„ฑ ์š”์†Œ์™€ ์—ญํ• 


1. ์ƒ์‚ฐ์ž(Producer): ๋ฐ์ดํ„ฐ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๊ณต์œ  ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ(์˜ˆ: ํ ๋˜๋Š” ๋ฒ„ํผ)์— ๋ฐ์ดํ„ฐ๋ฅผ ๋„ฃ์Šต๋‹ˆ๋‹ค.
2. ์†Œ๋น„์ž(Consumer): ๊ณต์œ  ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™€ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
3. ๊ณต์œ  ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ(Shared Data Structure): ์ƒ์‚ฐ์ž์™€ ์†Œ๋น„์ž๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ๊ณ ๋ฐ›๋Š” ๊ณตํ†ต์˜ ์ €์žฅ์†Œ์ž…๋‹ˆ๋‹ค. ์ด ๊ตฌ์กฐ๋Š” ์Šค๋ ˆ๋“œ ๊ฐ„ ๋™๊ธฐํ™”๋ฅผ ๊ด€๋ฆฌํ•˜๊ณ  ๋ฐ์ดํ„ฐ๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ๋ณด๊ด€ํ•˜๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.
4. ๋™๊ธฐํ™” ๋ฉ”์ปค๋‹ˆ์ฆ˜(Synchronization Mechanism): ์Šค๋ ˆ๋“œ ๊ฐ„์˜ ์•ˆ์ „ํ•œ ๋ฐ์ดํ„ฐ ๊ณต์œ ๋ฅผ ์œ„ํ•ด ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ๋Œ€ํ‘œ์ ์œผ๋กœ ๋ฎคํ…์Šค(mutex)๋‚˜ ์„ธ๋งˆํฌ์–ด(semaphore)์™€ ๊ฐ™์€ ๋™๊ธฐํ™” ๋„๊ตฌ๋ฅผ ํ™œ์šฉํ•ฉ๋‹ˆ๋‹ค.

 

์ถœ์ฒ˜: https://coding-food-court.tistory.com/81

 

์ƒ์‚ฐ์ž-์†Œ๋น„์ž ํŒจํ„ด์˜ ์žฅ์ 

- ์„ฑ๋Šฅ ํ–ฅ์ƒ: ๋ฐ์ดํ„ฐ ์ƒ์„ฑ๊ณผ ์ฒ˜๋ฆฌ๋ฅผ ๋ณ‘๋ ฌ๋กœ ์‹คํ–‰ํ•˜์—ฌ ์‹œ์Šคํ…œ ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค.
- ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ๋ถ„๋ฆฌ: ๋ฐ์ดํ„ฐ ์ƒ์„ฑ๊ณผ ์ฒ˜๋ฆฌ ๋กœ์ง์„ ๋ถ„๋ฆฌํ•จ์œผ๋กœ์จ ์ฝ”๋“œ ์œ ์ง€ ๊ด€๋ฆฌ์™€ ํ™•์žฅ์„ฑ์„ ํ–ฅ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค.
- ์ž์› ๊ด€๋ฆฌ: ๊ณต์œ  ์ž์›์— ๋Œ€ํ•œ ์•ˆ์ „ํ•œ ์ ‘๊ทผ์„ ๋ณด์žฅํ•˜์—ฌ ๊ฒฝํ•ฉ ์กฐ๊ฑด(race condition) ๋ฐ ๋ฐ๋“œ๋ฝ(deadlock) ๊ฐ™์€ ๋‹ค์ค‘ ์Šค๋ ˆ๋“œ ๋ฌธ์ œ๋ฅผ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค.

Producer-Consumer ํŒจํ„ด์€ ๋‹ค์–‘ํ•œ ์–ธ์–ด์™€ ํ™˜๊ฒฝ์—์„œ ๊ตฌํ˜„๋  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋”ฉ, ๋ฉ€ํ‹ฐํ”„๋กœ์„ธ์‹ฑ, ๋ถ„์‚ฐ ์‹œ์Šคํ…œ ๋“ฑ์—์„œ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ์ด ํŒจํ„ด์„ ์ž˜ ์ดํ•ดํ•˜๊ณ  ํ™œ์šฉํ•˜๋ฉด ๋ณต์žกํ•œ ๋‹ค์ค‘ ์Šค๋ ˆ๋“œ ํ™˜๊ฒฝ์—์„œ๋„ ์•ˆ์ •์ ์ด๊ณ  ํšจ์œจ์ ์ธ ์†Œํ”„ํŠธ์›จ์–ด๋ฅผ ๊ฐœ๋ฐœํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค.

 

 

 

 

์Šค๋ ˆ๋“œํ’€(Thread Pool)

์Šค๋ ˆ๋“œ ํ’€(Thread Pool)์€ ๋‹ค์ค‘ ์Šค๋ ˆ๋“œ ํ™˜๊ฒฝ์—์„œ ์ž‘์—…์„ ๊ด€๋ฆฌํ•˜๊ณ  ์‹คํ–‰ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋˜๋Š” ์†Œํ”„ํŠธ์›จ์–ด ์Šคํ‚ฌ์ž…๋‹ˆ๋‹ค. ์ฃผ๋กœ ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ ์„œ๋ฒ„ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๊ณผ ๊ฐ™์ด ๋™์‹œ์— ์—ฌ๋Ÿฌ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•˜๋Š” ์ƒํ™ฉ์—์„œ ์œ ์šฉํ•˜๊ฒŒ ํ™œ์šฉ๋ฉ๋‹ˆ๋‹ค. ์•ž์„œ ์†Œ๊ฐœํ•œ ์ƒ์‚ฐ์ž-์†Œ๋น„์ž ํŒจํ„ด์„ ์Šค๋ ˆ๋“œํ’€๋กœ ๊ตฌํ˜„ํ•  ์˜ˆ์ •์ž…๋‹ˆ๋‹ค.


์Šค๋ ˆ๋“œ ํ’€์˜ ๊ตฌ์„ฑ ์š”์†Œ

1. ์Šค๋ ˆ๋“œ ์ง‘ํ•ฉ(workers): ์Šค๋ ˆ๋“œ ํ’€์€ ๋ฏธ๋ฆฌ ์ƒ์„ฑ๋œ ์Šค๋ ˆ๋“œ ์ง‘ํ•ฉ(workers)์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ์Šค๋ ˆ๋“œ๋“ค์€ ์ž‘์—…์„ ์‹คํ–‰ํ•  ์ค€๋น„๊ฐ€ ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ์Šค๋ ˆ๋“œ ํ’€์˜ ํฌ๊ธฐ๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ์„ค์ • ๊ฐ€๋Šฅํ•˜๋ฉฐ, ์ด ํฌ๊ธฐ๋Š” ์‹œ์Šคํ…œ ์ž์›๊ณผ ์ž‘์—… ๋ถ€ํ•˜์— ๋”ฐ๋ผ ์กฐ์ ˆํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

2. ์ž‘์—… ํ(job queue): ์Šค๋ ˆ๋“œ ํ’€์€ ์ž‘์—…์„ ์ €์žฅํ•˜๋Š” ํ๋ฅผ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ์ด ํ๋Š” ์Šค๋ ˆ๋“œ ํ’€์— ์ œ์ถœ๋œ ์ž‘์—…๋“ค์„ ์ €์žฅํ•˜๊ณ  ๋Œ€๊ธฐ์‹œํ‚ต๋‹ˆ๋‹ค. ์ž‘์—…์€ ํ์— ์ถ”๊ฐ€๋˜๋ฉด ์Šค๋ ˆ๋“œ ํ’€ ๋‚ด์˜ ์Šค๋ ˆ๋“œ๋“ค์ด ์ˆœ์„œ๋Œ€๋กœ ์ž‘์—…์„ ๊ฐ€์ ธ์™€ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

3. ์ž‘์—… ์ œ์ถœ ์ธํ„ฐํŽ˜์ด์Šค(inqueue): ํด๋ผ์ด์–ธํŠธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ์Šค๋ ˆ๋“œ ํ’€์— ์ž‘์—…์„ ์ œ์ถœํ•˜๋Š” ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ž‘์—…์„ ์Šค๋ ˆ๋“œ ํ’€์— ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ž‘์—…์€ ์Šค๋ ˆ๋“œ ํ’€์— ์ œ์ถœ๋˜๋ฉด ํ์— ์ถ”๊ฐ€๋˜์–ด ์Šค๋ ˆ๋“œ ํ’€ ๋‚ด์˜ ์Šค๋ ˆ๋“œ๋“ค์ด ์ฐจ๋ก€๋Œ€๋กœ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

์ถœ์ฒ˜: https://tecoble.techcourse.co.kr/post/2021-09-18-java-thread-pool/

 

 

 

 

์Šค๋ ˆ๋“œ ํ’€ ๊ตฌํ˜„

๋จผ์ € ์Šค๋ ˆ๋“œ ํ’€์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•œ ๊ธฐ์ˆ ์€ std::thread, std::condition_variable, std::mutex, std::queue, std::vector, template ์ •๋„๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ดˆ๋ณด์ž๋ถ„๋“ค์€ ๊ตฌํ˜„ํ•˜๊ธฐ ์‰ฝ์ง€ ์•Š์ง€๋งŒ ์Šค๋ ˆ๋“œ ํ’€์˜ ๊ฒฝ์šฐ ํ•œ ๋ฒˆ๋งŒ ์ดํ•ดํ•˜๊ณ  ์ž‘์„ฑํ•œ๋‹ค๋ฉด ์–ด๋””์„œ๋“  ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ฌด๊ธฐ๊ฐ€ ๋  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

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

class ThreadPool {
public:
    ThreadPool(size_t numThreads) : stop(false) {
        for (size_t i = 0; i < numThreads; ++i) {
            workers.emplace_back([this] {
                while (true) {
                    std::function<void()> task;

                    {
                        std::unique_lock<std::mutex> lock(queueMutex);
                        condition.wait(lock, [this] { return stop || !tasks.empty(); });

                        if (stop && tasks.empty()) {
                            return;
                        }

                        task = std::move(tasks.front());
                        tasks.pop();
                    }

                    task();
                }
            });
        }
    }

    template <class F>
    void enqueue(F&& func) {
        {
            std::unique_lock<std::mutex> lock(queueMutex);
            tasks.emplace(std::forward<F>(func));
        }
        condition.notify_one();
    }

    ~ThreadPool() {
        {
            std::unique_lock<std::mutex> lock(queueMutex);
            stop = true;
        }
        condition.notify_all();

        for (std::thread& worker : workers) {
            worker.join();
        }
    }

private:
    std::vector<std::thread> workers;
    std::queue<std::function<void()>> tasks;

    std::mutex queueMutex;
    std::condition_variable condition;
    bool stop;
};

int main() {
    ThreadPool pool(10); // 10๊ฐœ์˜ ์Šค๋ ˆ๋“œ๋ฅผ ๊ฐ€์ง„ ์Šค๋ ˆ๋“œ ํ’€ ์ƒ์„ฑ

    // ์ž‘์—…์„ ์Šค๋ ˆ๋“œ ํ’€์— ์ถ”๊ฐ€
    for (int i = 0; i < 30; ++i) {
        pool.enqueue([i] {
            std::cout << "Task " << i << " executed by thread " << std::this_thread::get_id() << std::endl;
        });
    }

    // ๋ชจ๋“  ์ž‘์—…์ด ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ
    std::this_thread::sleep_for(std::chrono::seconds(2));

    return 0;
}

 

๊ฐ„๋‹จํ•œ ์Šค๋ ˆ๋“œํ’€์„ ๊ตฌํ˜„ํ•œ ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค. ์Šค๋ ˆ๋“œํ’€์˜ ์ƒ์„ฑ์ž์—๋Š” ์ƒ์„ฑํ•  ์Šค๋ ˆ๋“œ์˜ ๊ฐœ์ˆ˜๋ฅผ ์ž…๋ ฅ๋ฐ›์•„์„œ ๊ฐœ์ˆ˜๋งŒํผ ์Šค๋ ˆ๋“œ๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ์Šค๋ ˆ๋“œ๊ฐ€ ์‹คํ–‰๋˜๋Š” ํ•จ์ˆ˜๋Š” ๋žŒ๋‹คํ•จ์ˆ˜๋กœ ์ฆ‰์‹œ ๋งŒ๋“ค์–ด์„œ ํ˜ธ์ถœํ–ˆ์Šต๋‹ˆ๋‹ค. ์ƒ์„ฑ๋œ ์Šค๋ ˆ๋“œ์—์„œ๋Š” notify๋ฅผ ๋Œ€๊ธฐํ•˜๊ณ  ์žˆ๋‹ค๊ฐ€ notify๊ฐ€ ๋˜๋ฉด ์žกํ(Job-queue)์—์„œ task๋ฅผ ๋นผ์„œ(front์™€ pop) ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค. ์ƒ์‚ฐ์ž ๋ถ€๋ถ„(enqueue ํ•จ์ˆ˜)์—์„œ๋Š” ํ์— ์‹คํ–‰๋  ๋‚ด์šฉ์„ ๋„ฃ๊ณ  ๊ธฐ๋‹ค๋ฆฌ๊ณ  ์žˆ๋Š” workers(๋Œ€๊ธฐ ์Šค๋ ˆ๋“œ)์—๊ฒŒ notify ํ†ต์ง€๋ฅผ ๋‚ ๋ฆฝ๋‹ˆ๋‹ค.

 

์Šค๋ ˆ๋“œ ํ’€์˜ ์†Œ๋ฉธ์ž์—์„œ๋Š” ๋Œ€๊ธฐํ•˜๊ณ  ์žˆ๋Š” ๋ชจ๋“  ์Šค๋ ˆ๋“œ๋“ค์„ ๊นจ์›Œ์„œ joinํ•˜๊ธฐ ์œ„ํ•ด stopํ”Œ๋ž˜๊ทธ๋ฅผ ์ผฌ๊ณผ ๋™์‹œ์— notify_all์„ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ์ดํ›„ ๋ชจ๋“  workers์— ๋Œ€ํ•ด join์œผ๋กœ ์Šค๋ ˆ๋“œ๋ฅผ ์†Œ๋ฉธ์‹œํ‚ต๋‹ˆ๋‹ค. ์œ„ ์˜ˆ์ œ์˜ Task์—์„œ๋Š” ๊ฐ„๋‹จํžˆ ์Šค๋ ˆ๋“œ id์™€ ํ•จ๊ป˜ ๋ฉ”์„ธ์ง€๋ฅผ ์ถœ๋ ฅํ–ˆ์Šต๋‹ˆ๋‹ค. 10๊ฐœ์˜ ์Šค๋ ˆ๋“œ๋ฅผ ์ƒ์„ฑ์‹œ์ผฐ๊ณ , 30๊ฐœ์˜ task๋ฅผ ์‹คํ–‰์‹œ์ผฐ์Šต๋‹ˆ๋‹ค.

 

 

๋งŒ์•ฝ ์—ฌ๋Ÿฌ๋ถ„์ด ๋Œ€๊ทœ๋ชจ ์„œ๋น„์Šค๋ฅผ ์šด์˜ํ•œ๋‹ค๊ณ  ๊ฐ€์ •ํ–ˆ์„ ๋•Œ, ๋™์‹œ์— ๋“ค์–ด์˜ค๋Š” ์š”์ฒญ์ด ๋งŽ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด์— ๋Œ€ํ•ด์„œ ๋น ๋ฅด๊ฒŒ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋ณ‘๋ ฌ์ฒ˜๋ฆฌ๋Š” ํ•„์ˆ˜์ž…๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ํ”„๋กœ์„ธ์Šค์˜ ์ž์›์„ ๊ณ ๋ คํ–ˆ์„ ๋•Œ ์Šค๋ ˆ๋“œ ํ’€์ด ์–ด๋–ค ๊ฒƒ ๋ณด๋‹ค๋„ ํšจ๊ณผ์ ์ž…๋‹ˆ๋‹ค. ๋Œ€๊ทœ๋ชจ ์š”์ฒญ์ด ๋“ค์–ด์˜ค๋”๋ผ๋„, ์ƒ์‚ฐ์ž๋Š” Task๋ฅผ ํ์— ๋„ฃ๊ธฐ๋งŒ ํ•˜๊ณ , ์†Œ๋น„์ž(workers)๊ฐ€ Task๋ฅผ ์ฒ˜๋ฆฌํ•จ์œผ๋กœ์จ ์ด๋ฅผ ์†Œํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด์ฒ˜๋Ÿผ ์Šค๋ ˆ๋“œํ’€์€ ํ”„๋กœ๊ทธ๋žจ์˜ ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ค๊ณ  ์‹œ์Šคํ…œ ์•ˆ์ •์„ฑ์„ ์œ ์ง€ํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

 


 

 

 

๋‹ค์Œ ํฌ์ŠคํŒ…์—์„œ๋Š” ํ…œํ”Œ๋ฆฟ(Template)์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค^^

๋„์›€์ด ๋˜์…จ๊ธธ ๋ฐ”๋ž„๊ป˜์š”!

 

๋Œ“๊ธ€