반응형

std::thread 사용법 정리 (C++17 기준)

1. 기본 개념

  • C++11부터 도입된 std::thread는 병렬 실행을 위한 클래스입니다.
  • 쓰레드를 생성하면 자동으로 시작되며, 작업이 끝날 때까지 join() 또는 detach() 호출로 관리해야 합니다.

2. 기본 예제

#include <iostream>
#include <thread>

void say_hello() {
    std::cout << "Hello from thread!" << std::endl;
}

int main() {
    std::thread t(say_hello); // 쓰레드 시작
    t.join();                 // 메인 쓰레드가 t를 기다림
}
  • join()이 없으면 프로그램이 끝나기 전에 쓰레드가 종료되지 않을 수 있어 런타임 오류 발생.

3. 인자 전달

void print_sum(int a, int b) {
    std::cout << a + b << std::endl;
}

int main() {
    std::thread t(print_sum, 3, 4);
    t.join();
}
  • 값은 복사되어 전달됩니다.
  • 참조를 넘기려면 std::ref() 사용
void modify(int& x) {
    x += 10;
}

int main() {
    int value = 5;
    std::thread t(modify, std::ref(value));
    t.join();
    std::cout << value << std::endl; // 15
}

4. 람다 함수 사용

std::thread t([](){
    std::cout << "Running in a lambda thread!" << std::endl;
});
t.join();

5. 멤버 함수 호출

class Worker {
public:
    void run() {
        std::cout << "Worker running!" << std::endl;
    }
};

int main() {
    Worker w;
    std::thread t(&Worker::run, &w); // 객체 포인터 전달
    t.join();
}

6. join() vs detach()

  • join(): 쓰레드가 끝날 때까지 기다림
  • detach(): 쓰레드를 백그라운드에서 실행시키고 제어를 넘김
std::thread t([]() {
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "Detached thread done" << std::endl;
});
t.detach(); // join하지 않아도 됨

주의: detach한 쓰레드는 더 이상 제어할 수 없으며, 메인 함수가 먼저 종료되면 문제가 생길 수 있음.


7. 쓰레드 ID 확인

 
std::cout << std::this_thread::get_id() << std::endl;

8. 쓰레드 관련 함수들

  • std::this_thread::sleep_for(duration): 일정 시간 동안 sleep
  • std::this_thread::yield(): 다른 쓰레드에게 CPU 양보
  • std::thread::hardware_concurrency(): 시스템의 논리 코어 수 반환

9. 예외 처리

std::thread 자체는 예외를 전달하지 않기 때문에, 예외는 쓰레드 내부에서 처리해야 합니다.

std::thread t([]() {
    try {
        throw std::runtime_error("error!");
    } catch (const std::exception& e) {
        std::cout << "Caught exception: " << e.what() << std::endl;
    }
});
t.join();

10. C++17에서 유용한 추가 기능

  • std::scoped_lock을 쓰레드와 뮤텍스에 함께 사용 가능
  • std::shared_mutex (읽기/쓰기 락)과 함께 사용 시 성능 최적화 가능
  • 쓰레드 안전한 std::atomic 변수와 병행 사용

 

std::shared_mutex란?

  • 동시에 여러 개의 쓰레드가 읽을 수 있도록 허용하지만,
  • 쓰기(수정) 시에는 단 하나의 쓰레드만 접근 가능하게 만드는 락입니다.

읽기: 병렬 허용
쓰기: 단독만 허용

예: std::mutex와 std::shared_mutex의 차이

일반 std::mutex 사용 시

cpp
std::mutex mtx; void read() {
std::lock_guard<std::mutex> lock(mtx); // 읽기 작업
}

읽기조차 직렬화됨 (읽기끼리도 동시에 접근 불가)


std::shared_mutex 사용 시

#include <shared_mutex>

std::shared_mutex shared_mtx;

void read() {
    std::shared_lock<std::shared_mutex> lock(shared_mtx); // 여러 쓰레드 동시에 가능
    // 읽기 작업
}

void write() {
    std::unique_lock<std::shared_mutex> lock(shared_mtx); // 단독 접근
    // 쓰기 작업
}

여러 쓰레드가 동시에 읽기 가능
쓰기 쓰레드는 단독 접근만 허용 (읽기 중인 쓰레드가 끝날 때까지 대기)

성능 최적화의 이유

쓰기보다 읽기가 많은 상황 (예: 캐시, 설정 조회 등)에서는:

  • std::mutex: 모든 작업이 직렬 → 비효율적
  • std::shared_mutex: 읽기 병렬화 가능 → 성능 향상
#include <iostream>
#include <thread>
#include <shared_mutex>
#include <vector>

std::shared_mutex shared_mtx;
int shared_data = 0;

void reader(int id) {
    std::shared_lock<std::shared_mutex> lock(shared_mtx);
    std::cout << "Reader " << id << " read value: " << shared_data << std::endl;
}

void writer(int value) {
    std::unique_lock<std::shared_mutex> lock(shared_mtx);
    shared_data = value;
    std::cout << "Writer wrote value: " << value << std::endl;
}

int main() {
    std::vector<std::thread> threads;

    // 읽기 쓰레드 여러 개
    for (int i = 0; i < 5; ++i)
        threads.emplace_back(reader, i);

    // 쓰기 쓰레드 하나
    threads.emplace_back(writer, 100);

    for (auto& t : threads) t.join();
}

'Develop > C&CPP' 카테고리의 다른 글

[cpp] atomic  (0) 2025.04.20
[cpp] mutex, lock  (0) 2025.04.20
[cpp] cpp17에서 달라진 점  (0) 2025.04.20
[cpp] cpp14에서 추가된 것  (0) 2025.04.20
[service] 윈도우 서비스 프로그램  (0) 2025.01.05
반응형

std::atomic 개념과 사용법 (C++17 기준)

1. std::atomic이란?

  • 동기화된 접근을 보장하는 데이터 타입 템플릿입니다.
  • 멀티스레드에서 **뮤텍스 없이도 변수 접근의 원자성(atomicity)**을 보장합니다.
  • std::atomic<int>, std::atomic<bool>, std::atomic<T> 등 다양한 타입으로 사용 가능.

2. 기본 사용법

#include <atomic>
#include <iostream>
#include <thread>

std::atomic<int> counter{0};

void increment() {
    for (int i = 0; i < 10000; ++i) {
        counter++;
    }
}

int main() {
    std::thread t1(increment);
    std::thread t2(increment);

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

    std::cout << "Final counter: " << counter << std::endl;
}
  • counter++는 원자적으로 수행됨 → 경쟁 조건(race condition) 없이 동작

3. 주요 연산

  • store(val): 값 설정
  • load(): 현재 값 반환
  • exchange(val): 기존 값을 새로운 값으로 바꾸고, 이전 값을 반환
  • compare_exchange_strong(expected, desired)
  • compare_exchange_weak(expected, desired): CAS(compare-and-swap) 연산
std::atomic<int> value{5};
int expected = 5;
bool success = value.compare_exchange_strong(expected, 10);
// 성공 시 value는 10이 되고 true 반환
 

4. 메모리 순서 (memory ordering)

  • 기본은 memory_order_seq_cst (가장 강력하고 안전한 순서)
  • 필요에 따라 memory_order_relaxed, acquire, release 등 지정 가능
counter.fetch_add(1, std::memory_order_relaxed); // 약한 메모리 순서

하지만 대부분의 경우 기본 순서를 사용하는 것으로 충분하며, 성능 최적화가 필요한 경우만 조절합니다.


5. std::atomic_flag (가장 가벼운 락)

  • 간단한 락 구현이나 spin lock 등에 사용
  • 초기화는 반드시 ATOMIC_FLAG_INIT로
std::atomic_flag flag = ATOMIC_FLAG_INIT;

void work() {
    while (flag.test_and_set(std::memory_order_acquire)) {
        // busy-wait
    }
    // critical section
    flag.clear(std::memory_order_release);
}

6. 뮤텍스 vs atomic

항목  mutex atomic
동기화 범위 임의의 코드 블록 변수 단위
오버헤드 상대적으로 큼 작음
복잡한 동작 가능 제한적
예외 안전성 RAII로 안전 직접 관리 필요

7. C++17에서의 특징

  • 템플릿 deduction을 사용할 수 없지만, std::atomic<T>의 인터페이스는 보다 풍부해졌습니다.
  • std::atomic<std::shared_ptr<T>>도 제공되어, 참조 카운팅이 원자적으로 가능해졌습니다.

'Develop > C&CPP' 카테고리의 다른 글

[cpp] thread  (0) 2025.04.20
[cpp] mutex, lock  (0) 2025.04.20
[cpp] cpp17에서 달라진 점  (0) 2025.04.20
[cpp] cpp14에서 추가된 것  (0) 2025.04.20
[service] 윈도우 서비스 프로그램  (0) 2025.01.05
반응형

C++17에서의 mutex, lock

1. 기본 std::mutex 사용

std::mutex는 임계 구역(critical section)을 보호하기 위한 가장 기본적인 뮤텍스입니다.

#include <iostream>
#include <mutex>
#include <thread>

std::mutex mtx;

void print_message(const std::string& msg) {
    mtx.lock();
    std::cout << msg << std::endl;
    mtx.unlock();
}

int main() {
    std::thread t1(print_message, "Hello from thread 1");
    std::thread t2(print_message, "Hello from thread 2");

    t1.join();
    t2.join();
    return 0;
}
 

주의: lock()과 unlock()은 반드시 쌍으로 호출되어야 하며, 예외 발생 시 unlock이 호출되지 않아 데드락의 원인이 될 수 있음.


2. std::lock_guard 사용

std::lock_guard는 예외 안전성을 위해 사용되는 RAII 스타일의 뮤텍스 관리 도구입니다.

void print_message(const std::string& msg) {
    std::lock_guard<std::mutex> lock(mtx);
    std::cout << msg << std::endl;
}

스코프를 벗어나면 자동으로 unlock()이 호출됨.


3. std::unique_lock 사용

std::unique_lock은 lock_guard보다 더 유연한 뮤텍스 관리가 가능합니다. 예: 지연 잠금, 조건 변수와의 조합 등

void print_message(const std::string& msg) {
    std::unique_lock<std::mutex> lock(mtx);
    std::cout << msg << std::endl;
    // lock.unlock(); // 수동 해제도 가능
}

4. std::recursive_mutex

같은 쓰레드에서 여러 번 lock이 필요한 경우 사용합니다.

std::recursive_mutex rmtx;

void recursive_function(int count) {
    if (count <= 0) return;
    rmtx.lock();
    std::cout << "Depth: " << count << std::endl;
    recursive_function(count - 1);
    rmtx.unlock();
}

5. std::timed_mutex, std::recursive_timed_mutex

특정 시간 내에 lock을 얻지 못하면 포기하도록 설계된 뮤텍스입니다.

std::timed_mutex tmtx;

void try_lock_example() {
    if (tmtx.try_lock_for(std::chrono::milliseconds(100))) {
        std::cout << "Lock acquired" << std::endl;
        tmtx.unlock();
    } else {
        std::cout << "Timeout: failed to acquire lock" << std::endl;
    }
}

6. std::lock 함수

여러 뮤텍스를 동시에 안전하게 lock할 때 사용합니다. 데드락 방지를 위한 도구입니다.

데드락 케이스

// 쓰레드 A
m1.lock();
m2.lock(); // 잠금 대기 중

// 쓰레드 B
m2.lock();
m1.lock(); // 잠금 대기 중 → 데드락 발생!

쓰레드는 제 각각 돌기 때문에, 쓰레드A, B가 m1, m2를 lock을 한 다음 lock을 하게 되는 경우가 발생하게 되면 서로가 가진 뮤텍스를 기다리다가 영원히 대기 상태에 빠지게 됩니다.

#include <iostream>
#include <mutex>
#include <thread>

std::mutex m1, m2;

void threadA() {
    std::lock(m1, m2); // 안전하게 두 뮤텍스를 모두 잠금
    std::lock_guard<std::mutex> lk1(m1, std::adopt_lock);
    std::lock_guard<std::mutex> lk2(m2, std::adopt_lock);
    std::cout << "Thread A acquired both locks\n";
}

void threadB() {
    std::lock(m2, m1); // 순서를 바꿔도 안전하게 잠금
    std::lock_guard<std::mutex> lk1(m2, std::adopt_lock);
    std::lock_guard<std::mutex> lk2(m1, std::adopt_lock);
    std::cout << "Thread B acquired both locks\n";
}

int main() {
    std::thread t1(threadA);
    std::thread t2(threadB);

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

여기서 std::lock은 내부적으로 양쪽 쓰레드에서 같은 방식으로 동기화를 해주기 때문에 데드락 없이 둘 다 잠금에 성공할 수 있어요.


7. 주의사항 요약

  • 뮤텍스는 가능한 한 짧은 영역에서만 잠그고 풀어야 함
  • lock을 해제하지 않고 예외가 발생하면 데드락이 발생할 수 있음 → RAII 스타일 사용 권장
  • 여러 뮤텍스를 동시에 잠글 경우 반드시 std::lock 사용
  • 가능한 경우 std::scoped_lock (C++17부터 도입)을 사용하면 더 안전하게 여러 뮤텍스를 다룰 수 있음
std::mutex m1, m2;

void example() {
    std::scoped_lock lock(m1, m2);  // C++17: 데드락 방지 + RAII
    // 임계 구역
}
 

 

'Develop > C&CPP' 카테고리의 다른 글

[cpp] thread  (0) 2025.04.20
[cpp] atomic  (0) 2025.04.20
[cpp] cpp17에서 달라진 점  (0) 2025.04.20
[cpp] cpp14에서 추가된 것  (0) 2025.04.20
[service] 윈도우 서비스 프로그램  (0) 2025.01.05
반응형

C++17에서 달라진 점 (C++14와 비교)

C++17은 C++14에 비해 여러 가지 새로운 기능과 개선점을 도입하여, 개발자 생산성, 코드 가독성, 그리고 언어의 표현력을 크게 향상시켰습니다. 아래 표와 목록에서 주요 변경 사항을 정리합니다.

주요 변경점 비교

C++17
기능 c++14 c++17
Inline 변수 미지원 지원 (헤더 파일에서 변수 정의/초기화 가능)
구조적 바인딩 (Structured Bindings) 미지원 지원 (튜플, 구조체 분해 가능)
constexpr if 미지원 지원 (컴파일 타임 조건 분기)
Fold Expressions 미지원 지원 (가변 인자 템플릿 연산 간결화)
파일 시스템 라이브러리 미지원 지원 (std::filesystem)
병렬/동시 STL 알고리즘 미지원 지원 (병렬 실행 정책)
if/switch 초기화자 미지원 지원 (if/switch문에서 변수 선언 및 초기화)
중첩 네임스페이스 미지원 지원 (namespace a::b::c와 같이 간결하게 표현)
옵셔널 타입 미지원 지원 (std::optional)
클래스 템플릿 인자 추론(CTAD) 미지원 지원 (템플릿 인자 자동 추론)
인라인 변수 미지원 지원 (ODR 위반 없이 헤더에 변수 정의 가능)
 

C++17에서 추가된 주요 기능 및 개선 사항

  • Inline 변수: 헤더 파일에서 변수 정의/초기화 가능, ODR(One Definition Rule) 위반 방지
  • 구조적 바인딩 (Structured Bindings): 튜플, 구조체 등 복합 객체를 개별 변수로 분해할 수 있음
  •  
    cpp
    auto [x, y] = std::make_pair(1, 2);
  • constexpr if: 템플릿 코드에서 컴파일 타임 조건 분기 가능
  •  
    cpp
    if constexpr (조건) { /* ... */ }
  • Fold Expressions: 가변 인자 템플릿을 간결하게 연산할 수 있는 문법 도입
  •  
    cpp
    template<typename... Args> auto sum(Args... args) { return (args + ...); }
  • 파일 시스템 라이브러리(std::filesystem): 파일 및 디렉토리 작업을 표준화된 방식으로 지원
  • 병렬/동시 STL 알고리즘: 표준 라이브러리 알고리즘에 병렬 실행 정책 지원
  • if/switch 초기화자: if, switch문에서 변수 선언 및 초기화 가능
  •  
    cpp
    if (int x = func(); x > 0) { /* ... */ }
  • 중첩 네임스페이스: 여러 네임스페이스를 한 줄로 선언 가능
  •  
    cpp
    namespace a::b::c { /* ... */ }
  • std::optional: 값이 있을 수도 없을 수도 있는 상황을 명확하게 표현
  • 클래스 템플릿 인자 추론(CTAD): 객체 생성 시 템플릿 인자를 명시하지 않아도 자동 추론
  •  
    cpp
    std::pair p(1, 2); // std::pair<int, int>로 추론
  • 기타: std::variant, std::any, std::string_view 등 유틸리티 타입 추가, lambdas 개선, 기타 표준 라이브러리 확장

요약

  • C++14는 C++11의 기능을 다듬고 소폭 확장한 "마이너 업그레이드"였다면,
    C++17은 새로운 문법, 표준 라이브러리 확장, 컴파일 타임 프로그래밍 강화 등
    실질적인 "메이저 업그레이드"에 가까운 변화가 많습니다
  • 대부분의 기능은 기존 코드와의 호환성을 유지하면서,
    새로운 코드 작성 시 코드의 간결성, 안전성, 효율성을 크게 높여줍니다.

C++17로의 전환은 최신 컴파일러(GCC, Clang, MSVC 등)에서 모두 지원되며,
현대적인 C++ 코드를 작성하고자 한다면 적극적으로 권장됩니다

'Develop > C&CPP' 카테고리의 다른 글

[cpp] atomic  (0) 2025.04.20
[cpp] mutex, lock  (0) 2025.04.20
[cpp] cpp14에서 추가된 것  (0) 2025.04.20
[service] 윈도우 서비스 프로그램  (0) 2025.01.05
[server] ThreadPool, client 접속이 많을 때. TCP Server  (0) 2025.01.05
반응형

C++14에서 달라진 점 (C++11과 비교)

C++14는 C++11의 기능을 다듬고 확장한 표준으로, 대규모 기능 추가보다는 기존 기능의 개선과 편의성 향상에 초점을 맞췄습니다. 아래는 C++11 대비 C++14에서 달라진 주요 내용을 정리한 것입니다.

1. 람다 함수의 확장

  • 제네릭 람다(Generic Lambdas)
    • 람다 함수의 매개변수 타입에 auto 사용 가능
    •  
      cpp
      auto lambda = [](auto x, auto y) { return x + y; };
    • 템플릿 함수처럼 동작하는 람다 작성 가능

2. constexpr 기능 강화

  • C++11의 constexpr 함수는 단순한 리턴만 가능했으나,
    C++14에서는 if문, 반복문, 지역 변수 선언 등 복잡한 로직을 지원
  • 더 많은 함수와 상황에서 constexpr 사용 가능.

3. 함수 반환 타입 추론

  • Return Type Deduction
    • 함수의 반환 타입을 auto로 지정하면, 구현부의 return문을 기반으로 타입이 자동 추론됨
    •  
      cpp
      auto func() { return 42; }

4. 표준 라이브러리 및 기타 개선

  • Reader-Writer Locks
    • 다중 스레드 환경에서 읽기/쓰기 락 지원(std::shared_timed_mutex 등)
  • std::exchange
    • 객체의 값을 새로운 값으로 교체하고, 이전 값을 반환하는 함수 템플릿(라이브러리 확장).
  • 이진 리터럴 지원
    • 0b 또는 0B 접두사로 이진수 리터럴 사용 가능
    •  
      cpp
      int n = 0b101010;
  • [[deprecated]] 속성
    • 함수, 변수 등에 deprecated 속성 부여 가능

5. 기타 언어 및 코드 스타일 관련 변화

  • 변수 템플릿(Variable Templates)
    • 변수도 템플릿으로 선언 가능
  • decltype(auto)
    • auto와 decltype의 타입 추론 결합
  • 람다 캡처 초기화
    • 람다 캡처 시 임의의 표현식으로 멤버 초기화 가능
  • 이종 탐색(Heterogeneous Lookup)
    • 연관 컨테이너에서 키 타입과 다른 타입으로 검색 가능

요약 표

C++14 개선점 및 추가 기능
구분 C++11 C++14
람다 일반 람다 제네릭 람다(매개변수에 auto 사용)
constexpr 단순 리턴만 가능 if/반복문/지역변수 등 복잡한 로직 지원
함수 반환 타입 명시적 타입, 일부 auto 지원 모든 함수에 auto 반환 타입 추론
표준 라이브러리 기존 컨테이너, 락 Reader-Writer 락, std::exchange 등 추가
리터럴 10진/16진 등 이진 리터럴(0b) 지원
속성 없음 [[deprecated]] 속성 추가
타입/템플릿 함수/클래스 템플릿 변수 템플릿, decltype(auto)
람다 캡처 변수 캡처만 가능 표현식 기반 캡처(초기화) 지원
 

C++14는 C++11의 기반 위에서 실질적인 코드 작성의 편의성과 성능, 타입 추론의 유연성을 크게 높였습니다

반응형

MyService 로그램 용법

1. 비스 관 령어

 

  • 주요 기
  • 자동 시작 비스로 설치
  • 5초마다 상태 로
  • Ctrl+C로 버그 모드 종료 가능
     
  • 디버그 모드
  • 개발/테스트   -d  옵션으로 콘솔에서 직접 실행 가능
  • 로그가 콘솔에 출력
#define _CRT_SECURE_NO_WARNINGS
#pragma warning(disable: 4819)
#pragma warning(disable: 4100)

#include <windows.h>
#include <tchar.h>
#include <winsvc.h>
#include <iostream>
#include <fstream>
#include <chrono>
#include <ctime>
#include <string>
#include <filesystem>

// 상수 정의
#define SERVICE_NAME _T("MyService")
#define SERVICE_DISPLAY_NAME _T("My Service")
#define SERVICE_DESC _T("My Service Description")
#define LOG_FILE "c:\\mysvc.log"

// 전역 변수
static bool g_debug_mode = false;
static std::ofstream g_log_file;
static SERVICE_STATUS_HANDLE g_ssh = NULL;
static HANDLE g_stop_event = NULL;

// 유니코드 매크로
#ifdef UNICODE
#define tprintf wprintf
#define tstring std::wstring
#else
#define tprintf printf
#define tstring std::string
#endif

// 함수 선언
void MySetStatus(DWORD dstatus, DWORD daccept);
void ServiceHandler(DWORD dwcontrol);
void ServiceHandlerEx(DWORD dwcontrol, DWORD dweventtype, LPVOID lpeventdata, LPVOID lpcontext);
void ServiceMain();
VOID WINAPI ServiceMainProc(DWORD argc, LPTSTR* argv);
bool InstallService();
bool UninstallService();
BOOL MyStartService(tstring Name);
bool StopService();
BOOL WINAPI DebugConsoleHandler(DWORD signal);
bool KillService();

// 로깅 함수
void LogMessage(const std::string& message) {
    auto now = std::chrono::system_clock::now();
    auto time = std::chrono::system_clock::to_time_t(now);
    std::string timestamp = std::ctime(&time);
    timestamp.pop_back();

    std::string formatted_msg = "[" + timestamp + "] " + message + "\n";

    if (g_debug_mode) {
        std::cout << formatted_msg;
    }
    
    if (g_log_file.is_open()) {
        g_log_file << formatted_msg;
        g_log_file.flush();
    }
}

// 서비스 상태 관리 함수
void MySetStatus(DWORD dstatus, DWORD daccept) {
    SERVICE_STATUS ss = { 0, };
    if (!g_ssh) {
        LogMessage("MySetStatus: ERR status handle is null!");
        return;
    }
    ss.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
    ss.dwCurrentState = dstatus;
    ss.dwControlsAccepted = daccept;
    ss.dwWin32ExitCode = 10 * 1000;
    SetServiceStatus(g_ssh, &ss);
}

// 서비스 이벤트 핸들러
void ServiceHandler(DWORD dwcontrol) {
    LogMessage("ServiceHandler: control=" + std::to_string(dwcontrol));
    switch (dwcontrol) {
    case SERVICE_CONTROL_STOP:
        LogMessage("SERVICE_CONTROL_STOP received");
        MySetStatus(SERVICE_STOP_PENDING, SERVICE_ACCEPT_STOP);
        SetEvent(g_stop_event);
        break;
    default:
        MySetStatus(dwcontrol, SERVICE_ACCEPT_STOP);
        break;
    }
}

void ServiceHandlerEx(DWORD dwcontrol, DWORD dweventtype, LPVOID lpeventdata, LPVOID lpcontext) {
    LogMessage("ServiceHandlerEx: control=" + std::to_string(dwcontrol));
    switch (dwcontrol) {
    case SERVICE_CONTROL_SESSIONCHANGE:
        LogMessage("ServiceHandlerEx: session change");
        break;
    default:
        ServiceHandler(dwcontrol);
        break;
    }
}

// 서비스 메인 함수
void ServiceMain() {
    while (WaitForSingleObject(g_stop_event, 500) == WAIT_TIMEOUT) {
        LogMessage("Service is running...");
    }

    LogMessage("ServiceMain: End");
    if (!g_debug_mode) {
        MySetStatus(SERVICE_STOPPED, 0);
    }

    if (g_debug_mode) {
        CloseHandle(g_stop_event);
    }
}

VOID WINAPI ServiceMainProc(DWORD argc, LPTSTR* argv) {
    g_log_file.open(LOG_FILE, std::ios::app);
    LogMessage("Service started");

    g_ssh = RegisterServiceCtrlHandlerEx(SERVICE_NAME, (LPHANDLER_FUNCTION_EX)ServiceHandlerEx, NULL);
    LogMessage("ServiceMainProc: g_ssh=" + std::to_string((uint64_t)g_ssh));
    if (!g_ssh) {
        LogMessage("Failed to register service control handler");
        g_log_file.close();
        return;
    }

    MySetStatus(SERVICE_START_PENDING, 0);

    if (!g_stop_event) {
        g_stop_event = CreateEvent(NULL, TRUE, FALSE, NULL);
        if (!g_stop_event) {
            MySetStatus(SERVICE_STOPPED, 0);
            g_log_file.close();
            return;
        }
    }

    MySetStatus(SERVICE_RUNNING, SERVICE_ACCEPT_STOP);
    ServiceMain();
    g_log_file.close();
}

// 서비스 관리 함수들
bool InstallService() {
    SC_HANDLE scm = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
    if (!scm) return false;

    TCHAR path[MAX_PATH];
    GetModuleFileName(NULL, path, MAX_PATH);
    _tcscat_s(path, _T(" -svc"));

    SC_HANDLE service = CreateService(
        scm, SERVICE_NAME, SERVICE_DISPLAY_NAME,
        SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
        SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, path,
        NULL, NULL, NULL, NULL, NULL);

    if (service) {
        SERVICE_DESCRIPTION sd = {const_cast<LPTSTR>(SERVICE_DESC)};
        ChangeServiceConfig2(service, SERVICE_CONFIG_DESCRIPTION, &sd);
        CloseServiceHandle(service);
    }
    CloseServiceHandle(scm);
    return service != NULL;
}

bool UninstallService() {
    SC_HANDLE scm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
    if (!scm) return false;

    SC_HANDLE service = OpenService(scm, SERVICE_NAME, SERVICE_ALL_ACCESS);
    if (!service) {
        CloseServiceHandle(scm);
        return false;
    }

    SERVICE_STATUS status;
    ControlService(service, SERVICE_CONTROL_STOP, &status);
    bool result = DeleteService(service);

    CloseServiceHandle(service);
    CloseServiceHandle(scm);
    return result;
}

BOOL MyStartService(tstring Name) {
    SC_HANDLE schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
    if (!schSCManager) {
        DWORD err = GetLastError();
        LogMessage("OpenSCManager failed. Error: " + std::to_string(err));
        return FALSE;
    }

    SC_HANDLE schService = OpenService(schSCManager, Name.c_str(), SERVICE_START | SERVICE_QUERY_STATUS);
    if (!schService) {
        DWORD err = GetLastError();
        LogMessage("OpenService failed. Error: " + std::to_string(err));
        CloseServiceHandle(schSCManager);
        return FALSE;
    }

    // 서비스 상태 확인
    SERVICE_STATUS status;
    if (QueryServiceStatus(schService, &status)) {
        if (status.dwCurrentState == SERVICE_RUNNING) {
            LogMessage("Service is already running");
            CloseServiceHandle(schService);
            CloseServiceHandle(schSCManager);
            return TRUE;
        }
    }

    // 서비스 시작
    if (!StartService(schService, 0, NULL)) {
        DWORD err = GetLastError();
        if (err == ERROR_SERVICE_ALREADY_RUNNING) {
            LogMessage("Service is already running");
            CloseServiceHandle(schService);
            CloseServiceHandle(schSCManager);
            return TRUE;
        }
        LogMessage("StartService failed. Error: " + std::to_string(err));
        CloseServiceHandle(schService);
        CloseServiceHandle(schSCManager);
        return FALSE;
    }

    // 서비스가 실제로 시작될 때까지 대기
    int retries = 0;
    while (retries < 10) {  // 최대 10초 대기
        if (QueryServiceStatus(schService, &status)) {
            if (status.dwCurrentState == SERVICE_RUNNING) {
                LogMessage("Service started successfully");
                CloseServiceHandle(schService);
                CloseServiceHandle(schSCManager);
                return TRUE;
            }
            if (status.dwCurrentState == SERVICE_STOPPED) {
                LogMessage("Service failed to start");
                break;
            }
        }
        Sleep(1000);
        retries++;
    }

    CloseServiceHandle(schService);
    CloseServiceHandle(schSCManager);
    return FALSE;
}

bool StopService() {
    SC_HANDLE scm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
    if (!scm) return false;

    SC_HANDLE service = OpenService(scm, SERVICE_NAME, SERVICE_STOP | SERVICE_QUERY_STATUS);
    if (!service) {
        CloseServiceHandle(scm);
        return false;
    }

    SERVICE_STATUS status;
    bool result = ControlService(service, SERVICE_CONTROL_STOP, &status);
    
    // 서비스가 완전히 중지될 때까지 대기
    if (result) {
        while (QueryServiceStatus(service, &status)) {
            if (status.dwCurrentState == SERVICE_STOPPED) {
                break;
            }
            Sleep(1000);
        }
    }

    CloseServiceHandle(service);
    CloseServiceHandle(scm);
    return result;
}

// 디버그 모드 핸들러
BOOL WINAPI DebugConsoleHandler(DWORD signal) {
    if (signal == CTRL_C_EVENT || signal == CTRL_BREAK_EVENT) {
        LogMessage("Debug mode stop requested");
        if (g_stop_event) {
            SetEvent(g_stop_event);
        }
        return TRUE;
    }
    return FALSE;
}

// 서비스 강제 종료 함수 구현
bool KillService() {
    SC_HANDLE scm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
    if (!scm) {
        LogMessage("OpenSCManager failed in KillService");
        return false;
    }

    SC_HANDLE service = OpenService(scm, SERVICE_NAME, SERVICE_ALL_ACCESS);
    if (!service) {
        LogMessage("OpenService failed in KillService");
        CloseServiceHandle(scm);
        return false;
    }

    // 현재 서비스 상태 확인
    SERVICE_STATUS status = { 0 };
    if (!QueryServiceStatus(service, &status)) {
        LogMessage("QueryServiceStatus failed in KillService");
        CloseServiceHandle(service);
        CloseServiceHandle(scm);
        return false;
    }

    // 서비스가 이미 중지 상태면 종료
    if (status.dwCurrentState == SERVICE_STOPPED) {
        LogMessage("Service is already stopped");
        CloseServiceHandle(service);
        CloseServiceHandle(scm);
        return true;
    }

    // 서비스 강제 종료 후 상태를 STOPPED로 변경
    LogMessage("Forcing service to stop state");
    memset(&status, 0, sizeof(SERVICE_STATUS));
    status.dwCurrentState = SERVICE_STOPPED;
    ControlService(service, SERVICE_CONTROL_STOP, &status);

    CloseServiceHandle(service);
    CloseServiceHandle(scm);
    return true;
}

// 메인 함수
int main(int argc, char* argv[]) {
    if (argc > 1) {
        if (strcmp(argv[1], "-i") == 0) {
            return InstallService() ? 0 : 1;
        }
        else if (strcmp(argv[1], "-r") == 0) {
            return MyStartService(SERVICE_NAME) ? 0 : 1;
        }
        else if (strcmp(argv[1], "-s") == 0) {
            return StopService() ? 0 : 1;
        }
        else if (strcmp(argv[1], "-u") == 0) {
            return UninstallService() ? 0 : 1;
        }
        else if (strcmp(argv[1], "-d") == 0) {
            g_debug_mode = true;
            SetConsoleCtrlHandler(DebugConsoleHandler, TRUE);
            g_log_file.open(LOG_FILE, std::ios::app);
            LogMessage("Debug mode started");

            g_stop_event = CreateEvent(NULL, TRUE, FALSE, NULL);
            if (!g_stop_event) {
                g_log_file.close();
                return 1;
            }
            ServiceMain();
            g_log_file.close();
            return 0;
        }
        else if (strcmp(argv[1], "-svc") == 0) {
            SERVICE_TABLE_ENTRY serviceTable[] = {
                {const_cast<LPTSTR>(SERVICE_NAME), ServiceMainProc},
                {NULL, NULL}
            };
            return StartServiceCtrlDispatcher(serviceTable) ? 0 : 1;
        }
        else if (strcmp(argv[1], "-k") == 0) {
            return KillService() ? 0 : 1;
        }
    }
    return 0;
}

 

 

 

 

 

반응형

- 클라이언트 접속이 많은 경우, 부하 조정.  쓰레드 풀을 만들어 성능 관리. 동시 실행 쓰레드 수를 제한하여 시스템 과부하 방지.

- 쓰레드 생성 오버헤드 제거. 동시 접속자수 제한으로 서버 안정성 확보. 쓰레드 풀 크기 조정을 하여 성능 조정.

- ^C로 종료.

#include <iostream>
#include <thread>
#include <vector>
#include <queue>
#include <mutex>
#include <condition_variable>
#include <WinSock2.h>
#include <WS2tcpip.h>
#include <windows.h>
#include <string>
#include <sstream>
#include <atomic>
#include <csignal>
#include <chrono>

#pragma comment(lib, "ws2_32.lib")

#define SERVER_PORT 30000
#define BUFFER_SIZE 1024

#define MAX_THREADS 5           // 최대 스레드 수; CPU 코어 수 만큼 설정
#define MAX_QUEUE_SIZE 500     // 최대 큐 크기. 대기 클라이언트 수 만큼 설정.
#define MAX_CONNECTIONS (MAX_QUEUE_SIZE+MAX_THREADS+10)   // 최대 연결 수

#define SOCKET_TIMEOUT 3000    // 소켓 connect 타임아웃 (밀리초)
#define IDLE_TIMEOUT 10000  // 클라이언트 유휴 타임아웃 (밀리초)


// 디버그 및 에러 로그 매크로 정의
static bool g_debug_mode = false;
static bool g_error_mode = false;
static bool g_info_mode = true;

#define DEBUG_LOG(msg) \
    if (g_debug_mode) { \
        std::stringstream ss; \
        ss << "[DEBUG] " << msg; \
        std::cout << ss.str() << std::endl; \
    }

#define ERROR_LOG(msg) \
    if (g_error_mode) { \
        std::stringstream ss; \
        ss << "[ERROR] " << msg; \
        std::cerr << ss.str() << std::endl; \
    }

#define INFO_LOG(msg) \
    if (g_info_mode) { \
        std::stringstream ss; \
        ss << "[INFO] " << msg; \
        std::cout << ss.str() << std::endl; \
    }

// 전역 변수
static std::atomic<size_t> g_total_requests{0};
static size_t g_last_printed_count{0};
static std::chrono::steady_clock::time_point g_last_print_time;
static SOCKET g_serverSocket = INVALID_SOCKET;

class ThreadPool {
public:
    std::atomic<size_t> current_connections{0};  // 현재 총 연결 수
    std::atomic<size_t> active_threads{0};       // 현재 작업 중인 스레드 수

    size_t GetQueueSize() {
        std::lock_guard<std::mutex> lock(queue_mutex);
        return tasks.size();  // 대기 큐에 있는 클라이언트 수
    }

private:
    std::vector<std::thread> workers;
    std::queue<std::pair<SOCKET, sockaddr_in>> tasks;
    std::mutex queue_mutex;
    std::condition_variable condition;
    std::atomic<bool> stop{false};

    void HandleClient(SOCKET clientSocket, sockaddr_in clientAddr) {
        active_threads++;  // 작업 시작
        try {
            // 소켓 타임아웃 설정
            struct timeval timeout;
            timeout.tv_sec = IDLE_TIMEOUT / 1000;
            timeout.tv_usec = (IDLE_TIMEOUT % 1000) * 1000;
            
            if (setsockopt(clientSocket, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout)) < 0) {
                ERROR_LOG("Failed to set receive timeout");
            }
            if (setsockopt(clientSocket, SOL_SOCKET, SO_SNDTIMEO, (char*)&timeout, sizeof(timeout)) < 0) {
                ERROR_LOG("Failed to set send timeout");
            }

            char buffer[BUFFER_SIZE];
            char clientIP[INET_ADDRSTRLEN];
            inet_ntop(AF_INET, &clientAddr.sin_addr, clientIP, INET_ADDRSTRLEN);
            
            DEBUG_LOG("Client connected - IP: " << clientIP 
                      << ", Port: " << ntohs(clientAddr.sin_port));

            int bytesReceived = recv(clientSocket, buffer, BUFFER_SIZE, 0);
            if (bytesReceived == SOCKET_ERROR) {
                if (WSAGetLastError() == WSAETIMEDOUT) {
                    ERROR_LOG("Client " << clientIP << " timed out");
                } else {
                    ERROR_LOG("Receive error from " << clientIP);
                }
            } else if (bytesReceived > 0) {
                buffer[bytesReceived] = '\0';
                DEBUG_LOG("Received from " << clientIP << ": " << buffer);

                // 요청 처리 카운터 증가
                g_total_requests.fetch_add(1, std::memory_order_relaxed);

                std::string response;
                if (strcmp(buffer, "sysinfo") == 0) {
                    SYSTEM_INFO sysInfo;
                    GetSystemInfo(&sysInfo);
                    response = "Processor count: " + std::to_string(sysInfo.dwNumberOfProcessors);
                }
                else if (strcmp(buffer, "netinfo") == 0) {
                    char hostname[256];
                    gethostname(hostname, sizeof(hostname));
                    response = "Hostname: " + std::string(hostname);
                }
                else if (strcmp(buffer, "diskinfo") == 0) {
                    DWORD sectorsPerCluster, bytesPerSector, freeClusters, totalClusters;
                    GetDiskFreeSpace(NULL, &sectorsPerCluster, &bytesPerSector, 
                                    &freeClusters, &totalClusters);
                    response = "Disk total space: " + 
                              std::to_string((double)totalClusters * sectorsPerCluster * 
                                           bytesPerSector / (1024 * 1024 * 1024)) + " GB";
                }
                else {
                    response = "Unknown command";
                }

                send(clientSocket, response.c_str(), response.length(), 0);
            }

            DEBUG_LOG("Client disconnected - IP: " << clientIP);
        } catch (const std::exception& e) {
            ERROR_LOG("Exception in HandleClient: " << e.what());
        }
        
        closesocket(clientSocket);
        current_connections--;
        active_threads--;  // 작업 완료
    }

public:
    ~ThreadPool() {
        Shutdown();
    }

    void Shutdown() {
        DEBUG_LOG("ThreadPool shutdown initiated");
        
        stop = true;
        condition.notify_all();

        // 스레드 종료 대기 시작 시간 기록
        auto start = std::chrono::steady_clock::now();
        bool timeout = false;

        // worker 스레드들의 종료를 기다림
        for (std::thread& worker : workers) {
            if (worker.joinable()) {
                try {
                    // 현재 경과 시간 체크
                    auto now = std::chrono::steady_clock::now();
                    auto elapsed = std::chrono::duration_cast<std::chrono::seconds>(now - start).count();
                    if (elapsed >= 3) {  // 3초 초과
                        timeout = true;
                        break;
                    }

                    // 남은 시간만큼만 대기
                    worker.join();
                } catch (const std::exception& e) {
                    ERROR_LOG("Thread join error: " << e.what());
                }
            }
        }

        if (timeout) {
            ERROR_LOG("Thread shutdown timeout - forcing termination");
            // 남은 스레드들을 강제 분리
            for (std::thread& worker : workers) {
                if (worker.joinable()) {
                    worker.detach();
                }
            }
        }
        // 남은 작업들의 소켓을 정리
        std::unique_lock<std::mutex> lock(queue_mutex);
        while (!tasks.empty()) {
            auto& task = tasks.front();
            closesocket(task.first);
            tasks.pop();
        }
        current_connections = 0;
        DEBUG_LOG("ThreadPool shutdown completed");
    }

    // worker 스레드 로직 수정
    ThreadPool() {
        for (int i = 0; i < MAX_THREADS; ++i) {
            workers.emplace_back([this] {
                while (true) {
                    std::pair<SOCKET, sockaddr_in> task;
                    {
                        std::unique_lock<std::mutex> lock(queue_mutex);
                        condition.wait(lock, [this] { 
                            return stop || !tasks.empty(); 
                        });
                        
                        if (stop && tasks.empty()) {
                            DEBUG_LOG("Worker thread exiting");
                            return;
                        }
                        
                        if (!tasks.empty()) {
                            task = std::move(tasks.front());
                            tasks.pop();
                        }
                    }
                    
                    if (task.first != INVALID_SOCKET) {
                        HandleClient(task.first, task.second);
                    }
                }
            });
        }
    }

    bool EnqueueClient(SOCKET clientSocket, sockaddr_in clientAddr) {
        if (current_connections >= MAX_CONNECTIONS) {
            ERROR_LOG("Maximum connections reached");
            closesocket(clientSocket);
            return false;
        }
        current_connections++;

        std::unique_lock<std::mutex> lock(queue_mutex);
        
        if (tasks.size() >= MAX_QUEUE_SIZE) {
            char clientIP[INET_ADDRSTRLEN];
            inet_ntop(AF_INET, &clientAddr.sin_addr, clientIP, INET_ADDRSTRLEN);
            ERROR_LOG("Queue is full. Connection rejected from " << clientIP 
                     << ":" << ntohs(clientAddr.sin_port));
            closesocket(clientSocket);
            current_connections--;  // 연결 거부 시 카운터 감소
            return false;
        }

        tasks.push({clientSocket, clientAddr});
        lock.unlock();
        condition.notify_one();
        return true;
    }
};


// ThreadPool 클래스 전방 선언
class ThreadPool;
static ThreadPool* g_threadPool = nullptr;
static std::atomic<bool> g_running{true};

// 모니터링 함수
void MonitorRequests() {
    while (true) {
        std::this_thread::sleep_for(std::chrono::seconds(5));
        
        if (g_threadPool != nullptr) {
            size_t current_count = g_total_requests.load();
            size_t current_conns = g_threadPool->current_connections.load();
            size_t active_threads = g_threadPool->active_threads.load();
            size_t waiting_clients = g_threadPool->GetQueueSize();

            INFO_LOG("Server Status:"
                    "\n    Total requests processed: " << current_count <<
                    "\n    Current connections: " << current_conns <<
                    "\n    Active threads: " << active_threads << "/" << MAX_THREADS <<
                    "\n    Waiting clients: " << waiting_clients);
        }
    }
}



void SignalHandler(int signal) {
    static std::atomic<bool> shutting_down{false};
    if (shutting_down.exchange(true)) {
        return;
    }
    INFO_LOG("Signal " << signal << " received. Shutting down...");
    g_running = false;
    try {
        if (g_threadPool) {
            g_threadPool->Shutdown();
            // delete g_threadPool;
            g_threadPool = nullptr;
        }
    } catch (const std::exception& e) {
        ERROR_LOG("Exception in SignalHandler: " << e.what());
    }

    if (g_serverSocket != INVALID_SOCKET) {
        closesocket(g_serverSocket);
        g_serverSocket = INVALID_SOCKET;
    }

    WSACleanup();
    std::exit(0);
}

void GracefulShutdown() {
    INFO_LOG("Initiating graceful shutdown");
    g_running = false;
    
    // 새로운 연결 거부
    if (g_serverSocket != INVALID_SOCKET) {
        shutdown(g_serverSocket, SD_BOTH);
        closesocket(g_serverSocket);
    }
}

int main() {
    signal(SIGINT, SignalHandler);
    signal(SIGTERM, SignalHandler);
    // 디버그 모드 설정 (환경변수나 커맨드 라인 인자로도 설정 가능)
    g_debug_mode = false;  // 또는 true

    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        ERROR_LOG("WSAStartup failed");
        return 1;
    }

    SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, 0);
    g_serverSocket = serverSocket;  // 전역 변수에 저장

    int reuseAddr = 1; // 소켓 재사용 허용
    setsockopt(serverSocket, SOL_SOCKET, SO_REUSEADDR, 
               (const char*)&reuseAddr, sizeof(reuseAddr));

    struct linger lin;
    lin.l_onoff = 1;
    lin.l_linger = 0;  // 즉시 종료, TIME_WAIT 상태 방지
    setsockopt(serverSocket, SOL_SOCKET, SO_LINGER, 
               (const char*)&lin, sizeof(lin));

    sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(SERVER_PORT);
    serverAddr.sin_addr.s_addr = INADDR_ANY;

    if (bind(serverSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
        ERROR_LOG("Bind failed");
        closesocket(serverSocket);
        WSACleanup();
        return 1;
    }

    if (listen(serverSocket, SOMAXCONN) == SOCKET_ERROR) {
        ERROR_LOG("Listen failed");
        closesocket(serverSocket);
        WSACleanup();
        return 1;
    }

    // 타임아웃 설정
    struct timeval timeout;
    timeout.tv_sec = SOCKET_TIMEOUT / 1000;  // 초 단위
    timeout.tv_usec = (SOCKET_TIMEOUT % 1000) * 1000;  // 마이크로초 단위
    setsockopt(serverSocket, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout));
    setsockopt(serverSocket, SOL_SOCKET, SO_SNDTIMEO, (char*)&timeout, sizeof(timeout));

    // 모니터링 스레드 시작
    g_last_print_time = std::chrono::steady_clock::now();
    std::thread monitor_thread(MonitorRequests);
    monitor_thread.detach();  // 메인 스레드와 분리

    INFO_LOG("Server started on port " << SERVER_PORT);

    ThreadPool pool;
    g_threadPool = &pool;  // 전역 변수에 저장

    while (g_running) {
        sockaddr_in clientAddr;
        int clientAddrSize = sizeof(clientAddr);
        SOCKET clientSocket = accept(serverSocket, (sockaddr*)&clientAddr, &clientAddrSize);
        
        if (clientSocket == INVALID_SOCKET) {
            if (!g_running) break;  // 정상적인 종료 상황
            ERROR_LOG("Accept failed");
            continue;
        }

        if (!pool.EnqueueClient(clientSocket, clientAddr)) {
            char clientIP[INET_ADDRSTRLEN];
            inet_ntop(AF_INET, &clientAddr.sin_addr, clientIP, INET_ADDRSTRLEN);
            ERROR_LOG("Connection rejected from " << clientIP 
                     << ":" << ntohs(clientAddr.sin_port));
        }
    }
    INFO_LOG("Server end.");
    // 정상 종료 처리
    closesocket(serverSocket);
    WSACleanup();
    return 0;
}

 

 

반응형

client의 요청이 많을 때 동시접근 및 부하를 해결하기 위한 방안...

- 작업 중이면, 캐시 떠 놓은거 사용. 즉, 빠른 응답이 가능하도록. 

- read lock, write lock 으로 구분. read 작업은 동시 접근 가능. 

#include <iostream>
#include <vector>
#include <shared_mutex>
#include <optional>
#include <thread>
#include <chrono>
#include <atomic>
#include <string>
#include <windows.h>
#include <tlhelp32.h>

// 가정: PROCESS_INFO 구조체
struct PROCESS_INFO {
    int pid;
    std::string name;
};

std::vector<PROCESS_INFO> g_process;                      // 프로세스 목록
std::optional<std::vector<PROCESS_INFO>> g_process_cache; // 캐시된 프로세스 목록
std::shared_mutex process_mutex;                          // 동기화 도구
std::atomic<bool> is_updating{false};  // 업데이트 중인지 확인하는 플래그 추가

// 프로세스 목록을 갱신하는 함수
void update_process(int id) {
    if (is_updating.exchange(true)) {
        std::cout << "Update already in progress, skipping. :  thread id=" << id << "\n";
        return;
    }
    std::cout << "Update processing. :  thread id=" << id << "\n";

    std::vector<PROCESS_INFO> updated_process;
    
    // 프로세스 스냅샷 생성
    HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (snapshot != INVALID_HANDLE_VALUE) {
        PROCESSENTRY32W pe32;
        pe32.dwSize = sizeof(pe32);

        // 첫번째 프로세스 정보 가져오기
        if (Process32FirstW(snapshot, &pe32)) {
            do {
                // 와이드 문자열을 일반 문자열로 변환
                std::wstring wname(pe32.szExeFile);
                std::string name(wname.begin(), wname.end());
                
                // 프로세스 정보 추가
                updated_process.push_back({
                    static_cast<int>(pe32.th32ProcessID),
                    name
                });
            } while (Process32NextW(snapshot, &pe32));
        }
        CloseHandle(snapshot);
    }

    // 실제 데이터 업데이트
    {
        std::unique_lock<std::shared_mutex> lock(process_mutex);
        g_process = std::move(updated_process);
    }
    
    is_updating = false;
}

// 프로세스 목록을 출력하는 함수
void print_process(int id) {
    std::shared_lock<std::shared_mutex> lock(process_mutex);
    if (!g_process_cache || g_process_cache->size() != g_process.size()) {
        // 캐시가 없거나 크기가 다르면 캐시 업데이트
        g_process_cache = g_process;
    }
    lock.unlock();  // 캐시를 읽을 때는 잠금 해제

    // 캐시된 데이터 사용
    const auto& process_list = g_process_cache.value();
    std::cout << "Current Process List:  thread id=" << id << "\n";
    for (const auto& proc : process_list) {
        // std::cout << "PID: " << proc.pid << ", Name: " << proc.name << "\n";
    }
    std::cout << "Current Process Count : " << process_list.size() << "  thread id=" << id << "\n";
}

// 클라이언트 스레드
void client_thread(int id) {
    std::cout << "Client " << id << " started.\n";
    for (int i=0; i<10; i++  ) {
        update_process(id); // 프로세스 정보 갱신
        print_process(id);  // 최신 프로세스 정보 출력
        std::this_thread::sleep_for(std::chrono::milliseconds(rand()%100));   
    }
    std::cout << "Client " << id << " finished.\n";
}

int main() {

    update_process(0);

    // 여러 스레드에서 동작 시뮬레이션
    int thread_count = 10;
    std::vector<std::thread> threads;
    for(int i=0; i<thread_count; i++) {
        threads.push_back(std::thread(client_thread, i+1));
    }

    for(auto& thread : threads) {
        thread.join();
    }   

    return 0;
}

 

반응형
sort compare

Sort Algorithm Compare

  • 시험 방법
    정렬 알고리즘들을 500~550개(랜덤)의 값을 만들어 정렬하는 것을 5000번 반복한 시간을 측정하였다.
qsort test
ALG=selection_sort
OK cnt=5000 FAIL cnt=0
4029 ms
ALG=bubble_sort
OK cnt=5000 FAIL cnt=0
3190 ms
ALG=insertion_sort
OK cnt=5000 FAIL cnt=0
2257 ms
ALG=merge_sort
OK cnt=5000 FAIL cnt=0
361 ms
ALG=quick_sort
OK cnt=5000 FAIL cnt=0
236 ms
  • 랜덤한 값들을 정렬할 경우 알고리즘별로 시간 측정 결과 대략적으로 다음과 같은 순서로 성능이 좋았다. 퀵소트 이름이 괜히 퀵이 아니다.
  • quick > merge > insertion > bubble > selection
  • 위 결과는 정렬할 대상의 상태(값 배열)에 따라 달라질 수 있다.
#include <iostream>
#include <vector>
#include <ctime>
#include <chrono>

void selection_sort(int arr[], int size) {
    for(int i = 0 ; i < size - 1 ; i++) 
        for (int j = i + 1 ; j < size ; j++) 
            if(arr[i] > arr[j]) {
                // swap(arr[i], arr[j]);
                int t = arr[i] ;
                arr[i] = arr[j] ;
                arr[j] = t ;
            }
}


void bubble_sort(int a[], int n){
    int i, j, t;
    for (i = 0; i < n - 1; i++) {
        int bchanged=0 ;
        for (j = 1; j < n - i; j++) // j=(1,10)
        {
            if (a[j - 1] > a[j]){
                t = a[j - 1];
                a[j - 1] = a[j];
                a[j] = t;
                bchanged=1 ;
            }
        }
        if (bchanged==0) break ;
    }
}


void insertion_sort(int arr[], int size) {
    for(int i = 1 ; i <= size - 1 ; i++) 
        for (int j = i-1 ; j >= 0 ; j--) 
            if(arr[j] > arr[j+1]) {
                // swap(arr[j], arr[j+1]);
                int t = arr[j] ;
                arr[j] = arr[j+1] ;
                arr[j+1] = t ;
            } 
            else
                continue;
} 



void merge_sort(int arr[], int size) {
    // std::cout << "merge_sort size=" << size << std::endl ;

    if (size > 2) {
        // 왼쪽 반, 오른쪽 반을 나누어 반복.
        merge_sort(arr, size / 2);
        merge_sort(arr + size / 2, size - size / 2);
        
        // 왼쪽, 오른쪽 반이 각각 정렬된 상태임. merge한다.
        int leftCursor = 0;
        int rightCursor = size / 2;
        //int buff[50];
        int *buff = new int[size] ;

        int buffCursor = 0;
        // 두 그룹을 각각 스캐닝하면서 순서대로 기록. (어느 한쪽이 끝날때까지)
        while (leftCursor < size / 2 && rightCursor < size) {
            if (arr[leftCursor] < arr[rightCursor]) {
                buff[buffCursor++] = arr[leftCursor++];
            } else {
                buff[buffCursor++] = arr[rightCursor++];
            }
        }
        // 남은 한 그룹의 데이터를 append 
        for (int i = leftCursor ; i < size / 2 ; i++)
            buff[buffCursor++] = arr[i];
        for (int j = rightCursor ; j < size ; j++) 
            buff[buffCursor++] = arr[j];
        memcpy(arr, buff, size * sizeof(int));
        delete[] buff ;
    } else { // 원소 개수가 2개 이하인 경우, 비교하여 정렬한다.
        if (arr[0] > arr[1]) {
            // swap(arr[0], arr[1]);
            int tmp = arr[0] ;
            arr[0] = arr[1] ;
            arr[1] = tmp ;
        }
    }
}



// a: 정렬할 데이터 array
// n: 데이터 개수
void quick_sort(int a[], int n){
	int v, t;
	int i, j;
	if (n > 1){
		v = a[n - 1]; // v=축값. 마지막 노드값을 사용
		i = -1; 
		j = n - 1; 
		while (1){ // 분할
			// while (a[++i] < v); // 왼쪽에서부터 축값보다 크거나 같은 값이 있는지? -> 없다면??? 없을수는 없다. 자신끼리 비교가 마지막.
            while (a[++i]<v) {
                // std::cout << "debug : i=" << i << std::endl ; 
            }

			// while (a[--j] > v); // 오른쪽에서부터 축값보다 작거나 같은 값이 있는지? -> 가장작은값이 pivot이면?? index out range???
            while( a[--j]>v) {
                // std::cout << "debug : j=" << j << std::endl ; 
                // minus index로 갔는데 에러가 발생하지 않았지만, 쓸데없는 오버로드다. 다른 언어였으면 indexerror이다. 
                if (j==0)   // 전체 검색 완료이므로 더 이상 할 필요없다.
                    break ;
            }
			if (i >= j) 
                break; // i와 j의 위치가 뒤바뀌었으면 분할 끝. 좌우 분할이 이미 잘되어 있다. 
            // i와 j의 위치가 좌, 우 상태 그대로 라면 
			t = a[i]; // 두 값을 바꿈. 
			a[i] = a[j];
			a[j] = t;
		}
        // 피봇이 들어갈 자리는? 왼쪽을 기준으로 정렬이 다 된 상태에서 마지막 i자리에 큰 값이 와 있다. 
		t = a[i]; // 축 값과 축값의 위치에 있는 값을 바꿈
		a[i] = a[n - 1];
		a[n - 1] = t;
        // i위치 기준으로 좌우 대소 분할 완료.
		quick_sort(a, i); // 왼쪽 구간 퀵정렬. (i개만 한다. 즉, i위치는 개수에 포함 안함.)
		quick_sort(a + i + 1, n - i - 1); // 오른쪽 구간 퀵정렬 (index로 치면 a+i+1부터 a+n-1. 따라서 개수는 a+n-1 - (a+i+1)+1 )
	}
}



void print_array(int *a, int n) {
    for(int i=0; i<n; i++)
    {
        std::cout << a[i] << " " ;
    }
    std::cout << std::endl ;
}

int check_asc(int *a, int n) {
    if (n<=1) return 1 ;
    for (int i=1; i<n; i++) {
        if (a[i-1]>a[i])
            return 0 ;
    }
    return 1 ;
}

int test_alg(void (*pfunc)(int[], int)) {
    int cnt = std::rand()%50+500 ;
    int *a = new int[cnt] ;
    int bcheck=0 ;

    if (true) {
        // 랜덤값 생성
        for (int i=0; i<cnt; i++) {
            a[i]=  std::rand()%1000 ;
        }
    } else {
        // 이미 정렬된 상태값 생성
        for (int i=0; i<cnt; i++) {
            a[i]=  i ;
        }
    }

    // print_array(a,cnt) ; // 정렬 전.
    // (*pfunc)(a, cnt) ;   // 이렇게 해도 가능.
    pfunc(a,cnt) ;
    // print_array(a,cnt) ; // 정렬 후.
    bcheck = check_asc(a,cnt) ;

    delete[] a ;
    return bcheck ;
}


uint64_t getmilli() {
    using namespace std::chrono;
    return duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count() ;
}


int main() {

    std::cout << "sort test" << std::endl ;
    std::srand(std::time(NULL)) ;

    // int a[] = {3,8,5,1,9,4,2,6,7, 10} ;
    // int a[10] ;
    // for (int i=0; i<10; i++) {
    //     a[i] = std::rand()%50 ;
    // }
    // int n = sizeof(a)/sizeof(int) ;
    // print_array(a, n) ;
    // quick_sort(a, n) ;
    // merge_sort(a, n) ;
    // bubble_sort(a, n) ;
    // print_array(a, n) ;
    // return 0 ;

    int bcheck=0 ;
    int okcnt = 0, failcnt=0 ;
    uint64_t ts, te ;


    const char *pfunc_name[] = { "selection_sort",  "bubble_sort", "insertion_sort", "merge_sort",  "quick_sort",   NULL} ;
    void(*pfunc[])(int[], int) = { selection_sort, bubble_sort, insertion_sort, merge_sort, quick_sort,  NULL };
    int pfunc_cnt = sizeof(pfunc) / sizeof(pfunc[0]) ;

    for (int kk=0; kk<pfunc_cnt; kk++) {
        okcnt=0 ;
        failcnt=0 ;
        if (pfunc[kk]==NULL)
            break ;
        std::cout << "ALG=" << pfunc_name[kk] << std::endl ;

        ts = getmilli() ;
        for (int i=0; i<5000; i++) {
            bcheck = test_alg(pfunc[kk]) ;
            if (bcheck) {
                okcnt++ ;
                // std::cout << "OK" << std::endl ;
            } else {
                failcnt++ ;
                // std::cout << "FAIL" << std::endl ;
            }
        }
        te = getmilli() ;
        std::cout << "OK cnt=" << okcnt << "  FAIL cnt=" << failcnt << std::endl ;
        std::cout << te-ts << " ms" << std::endl ;
    }

    return 0 ;
}

만약 이미 정렬된 상태를 다시 정렬을 시키면?

qsort test
ALG=selection_sort
OK cnt=5000 FAIL cnt=0
1507 ms
ALG=bubble_sort
OK cnt=5000 FAIL cnt=0
16 ms
ALG=insertion_sort
OK cnt=5000 FAIL cnt=0
1439 ms
ALG=merge_sort
OK cnt=5000 FAIL cnt=0
164 ms
ALG=quick_sort
OK cnt=5000 FAIL cnt=0
1329 ms
  • 위 경우, bubble, merge가 성능이 좋았다. bubble이 좋았던 이유는 바로 이미 정렬되어 있는지를 처음 루프에서 알아낼 수 있기 때문에 그 조건으로 루프 탈출이 가능하기 때문이다.
  • 위 조건은 quick 소트 입장에서는 worst case이다. 먼저 이미 정렬되었는지 검사하는 루틴을 추가한다면 이런 경우를 방지할 수 있을 것이다.

Author: crazyj7@gmail.com

반응형

Quick Sort

시간복잡도 : O(Nlog_2N) 이지만 최악의 경우는 마찬가지로 O(N^2)

퀵 정렬은 아주 빠른 속도를 나타낼뿐만 아니라 원리도 간단해서 많은 응용 분야에서 사용되고 있다.
퀵 정렬은 연속적인 분할에 의해서 정렬한다.

축(Pivot)값을 중심으로 왼쪽은 이 축값보다 작은 값으로 오른쪽은 모두 이 축값보다 큰 값을 배열시키는 것이다.
이렇게 하여 축값의 왼쪽과 오른쪽 부분을 각각 또다시 분할하고 하는 과정을 분할의 크기가 1이 될 때까지 반복하면 전체적으로는 정렬이 완료된다.

-리스트 마지막 인덱스의 값을 pivot으로 정함.
피봇을 뺀 나머지 배열에서 가장 양쪽 끝 인덱스를 저장. 왼쪽은 i, 오른쪽은 j (인덱스 2개 사용)
i는 오른쪽으로 가면서 해당 값이 pivot보다 작아야 한다. 큰 것이 발견되면 멈춤.
j는 왼쪽으로 가면서 해당 값이 pivot보다 커야 한다. 작은 것이 발견되면 멈춤.
i위치와 j위치의 값을 바꿈.
그런데, i, j가 만나게 되는 경우. (i>=j) 해당 위치가 pivot이 들어갈 자리.
i위치의 값과 pivot 값 swap한다.
pivot을 기준으로 왼쪽, 오른쪽으로 리스트를 나누어, 각각 처음부터 다시 소팅한다.

알고리즘

  1. 만약 n>1 이면
  2. 1 N 크기의 a 배열을 분할 하여 축값의 위치를 mid로 넘긴다.
  3. 2 퀵 정렬 알고리즘(a, mid)
  4. 3 퀵 정렬 알고리즘 (a+mid-1, N-mid-1)
void quick_sort(int a[], int n){
    int v, t;
    int i, j;
    if (n > 1){
        v = a[n - 1]; // v=축값. 마지막 노드값을 사용
        i = -1; 
        j = n - 1; 
        while (1){ // 분할
            while (a[++i] < v); // 왼쪽에서부터 축값보다 크거나 같은 값이 있는지?
            while (a[--j] > v){ // 오른쪽에서부터 축값보다 작거나 같은 값이 있는지? -> 가장작은값이 pivot이면?? index out range 위험 아래 체크 필요.
                if (j==0) break ;
            }
            if (i >= j) break; // i와 j의 위치가 뒤바뀌었으면 분할 끝
            t = a[i]; // 아니면 두 값을 바꿈
            a[i] = a[j];
            a[j] = t;
        }
        t = a[i]; // 축 값과 축값의 위치에 있는 값을 바꿈
        a[i] = a[n - 1];
        a[n - 1] = t;
        quick_sort(a, i); // 왼쪽 구간 퀵정렬
        quick_sort(a + i + 1, n - i - 1); // 오른쪽 구간 퀵정렬
    }
}

퀵 정렬은 난수 배열이나 반쯤 정렬된 배열에 대해서는 빠른 속도를 보이지만 이미 정렬된 배열이나 역순 배열에 대해서는 성능이 엄청나게 떨어진다.
퀵 정렬의 이상적인 경우는 분할이 정확하게 구간을 양분하는 것이다. 이 경우 재귀의 깊이는 logN이 되며 가장 빠른 속도를 나타낸다.
이렇게 퀵 정렬은 분할할 축값이 어느 정도 그 구간의 중앙을 나누느냐에 성능이 좌우된다.
재귀 호출로 인해서 내부 스택이 사용된다. 따라서 퀵 정렬이 속도가 빠른 반면에 메모리의 희생이 있다.
N이 커지면 커질수록 스택의 크기가 커지며 스택 오버플로우가 발생할 수 있다.

성능개선: 이미 정렬된 상태인지를 체크하는 것을 추가한다.

ex)

0,1,2,3,4,5,6,7,8 : 인덱스
5,3,8,4,9,1,6,2,7 : 값

qsort

  1. 피봇을 마지막 번째 노드 값으로 정하자. (보통 첫번째나 마지막번째로 정함.)
    남은 데이터의 양쪽 끝에서 가운데로 스캔해 나가면서 왼쪽에서는 피봇보다 큰 값을 찾으면 멈추고, 오른쪽에서는 피봇보다 작은 값을 찾으면 멈추고, 둘을 교환한다.

pivot=7, scan index=0, 7 start find over or below than pivot.
stop index=2(큰값발견),7(작은값발견) value 8>7>2 ;swap
0,1,2,3,4,5,6,7,8
5,3,2,4,9,1,6,8,7

pivot=7, stop index=4, 6 value 9>7>6 ;swap
0,1,2,3,4,5,6,7,8
5,3,2,4,6,1,9,8,7

pivot=7, stop index=6> 5 ; cross
stop. index=5, 4 인덱스가 교차되면 멈추고, 피봇(오른쪽 파트위치로 인덱스를 잡았으므로 right part에 속해야 한다. left part의 마지막 스캔 위치 인덱스6, 피봇보다 큰 수를 처음 발견한 지점에 들어가야 한다. )과 left last index(6)와 swap
0,1,2,3,4,5,6,7,8
5,3,2,4,6,1,7,8,9
피봇을 기준으로 좌우 분리가 끝남.
피봇 위치가 바뀌면 한 싸이클이 끝난 것임. (pivot 7을 기준으로 좌(low)우(high) 분리 완료)

  1. 좌우 파트("532461", "89")에 대해 각각 qsort 수행. 파트의 원소 개수가 1이면 스킵.
    2-2.
    0 1 (실제 인덱스는 7,8)
    8 9
    pivot=9, stop=N, 0. 왼쪽값을 찾지 못함. 즉, 모두 작다. 이미 피봇은 가장 우측이라 변경할 필요가 없다. 리턴

2-1.
0,1,2,3,4,5
5,3,2,4,6,1
new pivot=1, start 0, 4

pivot=1, stop 0, N; 왼쪽에서는 큰 값을 찾았는데, 오른쪽에서는 작은 값을 찾지 못함. left part last index=0과 swap
0,1,2,3,4,5
1,3,2,4,6,5

  1. 좌우 파트 수행 : "", "32465". 왼쪽 파트는 1개이하라 스킵. 오른쪽 파트 수행
    0 1 2 3 4 (실제 인덱스 : 1,2,3,4,5)
    3 2 4 6 5
    new pivot=5 ; start 0, 3
    pivot=5, stop=3>2.cross! last left index=3. change
    0 1 2 3 4 (실제 인덱스 : 1,2,3,4,5)
    3 2 4 5 6

  2. 좌우 파트 수행 : "3 2 4", "6" ; 오른쪽 파트는 스킵.
    0 1 2 (실제 인덱스 : 1,2,3)
    3 2 4
    pivot=4, stop=N, 1 no swap. break

  3. 좌우 파트 수행 "32", ""
    0 1 (실제 인덱스 : 1,2)
    3 2
    pivot=2, start=0,0
    pivot=2, stop=0, N ; swap with index 0
    0 1 (실제 인덱스 : 1,2)

2 3
6. 좌우 분할 ; "", "3" ; 모두 리턴

0,1,2,3,4,5,6,7,8 : 인덱스
1,2,3,4,5,6,7,8,9 : 값

vector<double>  QuickSort(vector<double>& vec1){
  double i =  0;
  double j = vec1.size()-2;
  double tmp;
  int pivotindex = vec1.size()-1  ;
  double pivot = vec1[pivotindex];
  if  ( vec1.size()<=1  )
    return vec1 ;
  cout <<  "QuickSort: ";
  printvector(vec1)  ;

  while  (i <= j)  {
    while  (vec1[i]  < pivot)
      i++;
    while  (vec1[j]  > pivot)
      j--;
    if  (i <= j)  {
      tmp = vec1[i];
      vec1[i]  = vec1[j];
     vec1[j]  = tmp;
     i++;
     j--;
    }
  }

  // pivot change
  vec1[pivotindex]  = vec1[i]  ;
  vec1[i]=pivot ;
  pivotindex=i ;

  cout <<  "pivotting: ";
  printvector(vec1)  ;

  if  (vec1.size()<=2  )
       return vec1 ;

    // partition
    vector<double> left_vec, right_vec ;
    vector<double>::iterator pivotiter = vec1.begin()+pivotindex ;
    copy(vec1.begin(), pivotiter, back_inserter(left_vec))  ;
    copy(pivotiter+1, vec1.end(), back_inserter(right_vec))  ;

    cout <<  "left: ";
    printvector(left_vec)  ;
    cout <<  "right: ";
    printvector(right_vec)  ;

    if  (left_vec.size()>0  )  {
        QuickSort(left_vec);
        copy(left_vec.begin(), left_vec.end(), vec1.begin())  ;
    }

    if  (right_vec.size()>0  )  {
        QuickSort(right_vec);
        copy(right_vec.begin(), right_vec.end(), pivotiter+1)  ;
    }
    return vec1;
}

+ Recent posts