하아찡

[C++ 11] SleepLock 본문

C++/추가공부

[C++ 11] SleepLock

하아찡 2024. 12. 27. 14:46

이전에는 SpinLock을 살펴보았는데

이번에는 SleepLock을 살펴보겠습니다.

 

사실 SpinLock에 반복문 내부에 "일정시간 대기해라" 코드 한줄만 추가해주면 바로 SleepLock으로 변경하여 사용할 수 있습니다.

#include "pch.h"
#include <thread>
#include <atomic>
#include <mutex>


class SleepLock {
public:
    void lock() {

        bool expected = false;
        bool desired = true;

        while (!_locked.compare_exchange_weak(expected, desired)) {
            expected = false;

            //기존 SpinLock에서 SleepLock을 위해 추가된 코드
            this_thread::sleep_for(100ms);  //100ms동안 대기
        }
    }

    void unlock() {
        _locked.store(false);
    }

private:
    atomic<bool> _locked = false;
};

int32 sum = 0;
SleepLock sleepLock;
void Add() {
    for (int i = 0; i < 100000; i++) {
        lock_guard<SpinLock> guard(sleepLock);
        sum += i;
    }
}

void Sub() {
    for (int i = 0; i < 100000; i++) {
        lock_guard<SpinLock> guard(sleepLock);
        sum -= i;
    }
}
int main()
{
    mutex m;
    std::thread t1(Add);
    std::thread t2(Sub);

    
    if (t1.joinable()) {
        t1.join();
    }
    if (t2.joinable()) {
        t2.join();
    }

    cout << sum << endl;

}

 

기존에 SpinLock과 별 차이점이 없죠.

단지 Sleep이 추가돼서 해당 시간동안 CPU점유를 안하게됩니다.

 

긴 대기시간에 사용하면 좋겠죠?

 

물론 단점도 있습니다.

긴 대기시간동안 CPU를 점유하고 있으면 그것대로 단점이지만 SleepLock은 대기상태로 돌아가기 때문에 다른 쓰레드가 CPU를 점유할 수 있게 됩니다.
그 과정에서 문맥교환(Context Switching)이 발생합니다.

오히려 빠르게 점유했다가 풀어주는 상황에선 SpinLock을 사용하시는게 더 효과적입니다.

 

 

그러면 이러한 방식도 있지않을까요?

SpinLock을사용하다가 너무나도 오래동안 안나오면 SleepLock으로 변경해서 사용하는것도 가능하지 않을까요?

#include "pch.h"
#include <thread>
#include <atomic>
#include <mutex>


class AdaptiveLock {
public:
    void lock() {

        int32 spinCount = 0;         // Spin 횟수
        const int32 maxSpinCount = 1; // Spin 횟수 제한

        bool expected = false;
        bool desired = true;

        while (!_locked.compare_exchange_weak(expected, desired)) {
            expected = false;
            if (++spinCount > maxSpinCount) {
                cout << "Sleep으로 변경" << endl;
                std::this_thread::sleep_for(std::chrono::milliseconds(1)); // Sleep 전환
            }
            else {
                std::this_thread::yield(); // CPU 양보
            }


            
        }
    }

    void unlock() {
        _locked.store(false);
    }

private:
    atomic<bool> _locked = false;
};

int32 sum = 0;
AdaptiveLock adaptiveLock;
void Add() {
    for (int i = 0; i < 100000; i++) {
        lock_guard<AdaptiveLock> guard(adaptiveLock);
        sum += i;
    }
}

void Sub() {
    for (int i = 0; i < 100000; i++) {
        lock_guard<AdaptiveLock> guard(adaptiveLock);
        sum -= i;
    }
}
int main()
{
    mutex m;
    std::thread t1(Add);
    std::thread t2(Sub);
    std::thread t3(Add);
    std::thread t4(Sub);

    
    if (t1.joinable()) {
        t1.join();
    }
    if (t2.joinable()) {
        t2.join();
    }
    if (t3.joinable()) {
        t3.join();
    }
    if (t4.joinable()) {
        t4.join();
    }


    cout << sum << endl;

}

현재코드는 maxSpinCount 숫자를 낮췄습니다. 단순한 코드이다보닌깐 임의로 숫자를 낮추게됐습니다.

그래서 출력 결과를 살펴보면

결과값은 정상적으로 나오고 중간중간 Sleep으로 변경된 적이 있습니다.

위 코드처럼 일정 횟수가 넘어가면 SpinLock에서 SleepLock으로 변경도 가능하게 됩니다.

 

 

 

추가적인 예시

 

#include "pch.h"
#include <thread>
#include <atomic>
#include <mutex>


class AdaptiveLock {
public:
    void lock() {

        int32 spinCount = 0;         // Spin 횟수
        const int32 maxSpinCount = 100; // Spin 횟수 제한

        bool expected = false;
        bool desired = true;

        while (!_locked.compare_exchange_weak(expected, desired)) {
            expected = false;
            if (++spinCount > maxSpinCount) {
                //cout << "Sleep으로 변경" << endl;
                //std::this_thread::yield();
                std::this_thread::sleep_for(std::chrono::milliseconds(10000)); // Sleep 전환
            }
            else {
                std::this_thread::yield(); // CPU 양보
            }


            
        }
    }

    void unlock() {
        _locked.store(false);
    }

private:
    atomic<bool> _locked = false;
};

int32 sum = 0;
AdaptiveLock adaptiveLock;

queue<int32> q;
void Producer() {
    while (true) {
        {
            unique_lock<AdaptiveLock> guard(adaptiveLock);
            q.push(100);
        }

        std::this_thread::sleep_for(std::chrono::milliseconds(1000000));
    }
}

void Consumer() {
    while (true) {
        unique_lock<AdaptiveLock> guard(adaptiveLock);
        if (q.empty() == false) {
            //값이 있으면 빼온다
            int32 data = q.front();
            q.pop();
            cout << data << endl;
        }
    }
}
int main()
{
    mutex m;
    std::thread t1(Producer);
    std::thread t2(Consumer);
    std::thread t3(Consumer);
    std::thread t4(Consumer);
    std::thread t5(Consumer);


    
    if (t1.joinable()) {
        t1.join();
    }
    if (t2.joinable()) {
        t2.join();
    }
    if (t3.joinable()) {
        t3.join();
    }
    if (t4.joinable()) {
        t4.join();
    }
    if (t5.joinable()) {
        t5.join();
    }



    cout << sum << endl;

}

queue에 값을 넣는 속도가 느린데 값을 빼올라고 하는 작업을 계속하는데 SpinLock으로 돌리다가 계속 답이없으면 SleepLock으로 변경했을때 CPU 점유율 차이입니다.

 

SleepLock 전환 전

 

SleepLock 전환 후

 

반응형

'C++ > 추가공부' 카테고리의 다른 글

[C++] 메모리 모델  (0) 2024.12.27
[C++ 11] condition variable(조건 변수)  (0) 2024.12.27
[C++] Event  (0) 2024.12.27
[C++ 11]std::atomic, SpinLock  (1) 2024.12.27
Volatile 예약어  (0) 2024.10.30