muduo 라이브러리 학습을위한 다중 스레드 서버의 적용 가능한 경우

"서버 개발"은 모든 것을 포괄합니다.이 기사에서 "서버 개발"의 의미는 "공통 모델"기사를 참조하십시오. 한 문장으로 설명되어 있습니다. 사용자 인터페이스가없는 Linux 사용자 모드 장기 실행 네트워크 애플리케이션 멀티 코어 머신에서 실행됩니다. "장기 실행"은 프로그램이 연중 무휴로 다시 시작되지 않음을 의미하지는 않지만 할 일이 없기 때문에 프로그램이 종료되지 않고 다음 요청을 기다립니다. 예를 들어, wget은 장기 실행되지 않고 httpd는 장기 실행됩니다.

정류

이전 기사와 마찬가지로이 기사의 "프로세스"는 fork () 시스템 호출의 제품을 나타냅니다. "Thread"는 pthread_create ()의 제품을 말하며 제가 참조하는 pthread는 NPTL입니다. 각 스레드는 clone ()에 의해 생성되며 핵심 task_struct에 해당합니다. 이 기사에서 사용 된 개발 언어는 C ++이고 운영 환경은 Linux입니다.

우선, 여러 시스템으로 구성된 분산 시스템은 프로세스가 OS 경계를 넘을 수 없기 때문에 다중 프로세스 여야합니다 (문자 그대로 의미). 이 전제 하에서 우리는 최소 4 개의 코어가있는 일반 서버 인 하나의 시스템에 집중했습니다. 서비스를 제공하거나 멀티 코어 시스템에서 작업을 수행하려는 경우 사용 가능한 모드는 다음과 같습니다.

  1. 단일 스레드 프로세스 실행
  2. 다중 스레드 프로세스 실행
  3. 여러 단일 스레드 프로세스 실행
  4. 다중 스레드 프로세스 실행

이 모델 간의 비교는 이미 진부한 표현이며 간단히 요약하면 다음과 같습니다.

  • 모드 1은 확장 가능하지 않으며 (확장 가능) 멀티 코어 시스템의 컴퓨팅 성능을 사용할 수 없습니다.
  • 모드 3은 현재 주류 모드로 인식됩니다. 두 가지 하위 모드가 있습니다.
    • 3a 여러 tcp 포트를 사용하여 외부 서비스를 제공 할 수있는 경우 모드 1에서 프로세스의 여러 복사본을 실행하기 만하면됩니다.
    • 3b 주 프로세스 + 작업자 프로세스 (httpd + fastcgi와 같은 tcp 포트에 바인딩되어야하는 경우).
  • 모드 2는 다중 스레드 프로그램이 작성하기 어렵고 모드 3에 비해 이점이 없다고 생각하는 많은 사람들이 경멸합니다.
  • 모드 4는 훨씬 더 많이 사용되며 2와 3의 장점을 결합하지 못할뿐만 아니라 두 가지 단점을 모두 수렴합니다.

이 기사에서는 주로 모드 2와 모드 3b의 장단점, 즉 서버 프로그램이 다중 스레드 여야하는 경우에 대해 설명합니다.

기능적으로 말하면, 멀티 스레딩이 할 수있는 것은 단일 스레딩이 할 수없는 일 이며 그 반대의 경우도 마찬가지입니다. 그것들은 모두 상태 머신입니다 (반례를 보니 기쁩니다). 성능 측면에서 IO 바인딩이든 CPU 바인딩 서비스이든간에 멀티 스레딩은 이점이 없습니다 . 그렇다면 왜 멀티 스레딩을 사용합니까?

이 질문에 답하기 전에 단일 스레드를 사용해야하는 경우에 대해 이야기하겠습니다.

단일 스레드를 사용해야하는 경우

  • 프로그램은 fork()
  • 限制程序的 CPU 占用率
  • 看门狗进程단일 스레드 여야합니다.

내가 아는 한, 단일 스레드를 사용해야하는 두 가지 상황이 있습니다.

  1. 프로그램은 fork ()
  2. 프로그램의 CPU 사용량 제한

fork ()에 대해 먼저 이야기하겠습니다. " The Enlightenment of New Linux System Calls " 에서 언급 했습니다 .

Linux의 fork ()는 현재 스레드의 제어 스레드 만 복제하고 다른 스레드는 복제하지 않기 때문에 일반적으로 Fork ()는 다중 스레드 프로그램에서 호출 할 수 없습니다. 즉, fork ()는 부모 프로세스와 같은 다중 스레드 자식 프로세스를 동시에 생성 할 수 없으며 Linux에는 forkall ()과 같은 시스템 호출이 없습니다. forkall ()은 다른 스레드가 조건 변수를 기다리거나, 시스템 호출에서 차단 될 수 있거나, 뮤텍스가 임계 섹션으로 넘어갈 때까지 기다리거나, 집중적 인 계산에있을 수 있기 때문에 (구문 적으로) 실제로 수행하기 어렵습니다. 하위 프로세스로 이동하기에 충분하지 않습니다.

설상가상으로, 다른 스레드 a가 fork () 순간에 이미 뮤텍스를 획득했다면, fork ()에서 새 프로세스에 그러한 "스레드 a"가 없기 때문에이 뮤텍스는 절대로 해제되지 않습니다. new The 프로세스는 더 이상 해당 뮤텍스를 획득 할 수 없습니다. 그렇지 않으면 교착 상태가됩니다. (이 점은 추측 일 뿐이며 아직 실험이 수행되지 않았습니다. fork ()가 모든 뮤텍스를 해제 할 것이라는 점은 배제되지 않습니다.)

요약하면, fork ()를 호출하도록 설계된 프로그램은 "Revelation"기사에서 언급 한 "워치 독 프로세스"와 같이 단일 스레드 여야합니다. 다중 스레드 프로그램은 fork ()를 호출 할 수 없습니다. 그러나 그렇게하려면 많은 문제가 발생하므로 수행해야 할 이유를 생각할 수 없습니다.

일반적으로 프로그램 fork () 후에는 두 가지 동작이 있습니다.

  1. exec ()를 즉시 실행하고 다른 프로그램으로 변환하십시오. 예를 들어 shell 및 inetd; 또 다른 예는 lighttpd fork ()로 자식 프로세스를 포크 한 다음 fastcgi 프로그램을 실행합니다. 또는 클러스터의 컴퓨팅 노드에서 실행되는 작업을 시작하는 데몬 프로세스 ( "워치 독 프로세스"라고 함).
  2. exec ()를 호출하지 말고 현재 프로그램을 계속 실행하십시오. 공유 파일 설명자를 통해 상위 프로세스와 통신하여 협력하여 작업을 완료하거나 상위 프로세스에서 전달한 파일 설명자를 가져와 1980 년대 웹 서버 NCSA httpd와 같이 독립적으로 작업을 완료합니다.

이러한 동작 중 " 看门狗进程" 단일 스레드 여야하고 다른 동작은 기능 측면에서 다중 스레드 프로그램으로 대체 될 수 있다고 생각 합니다.

단일 스레드 프로그램은 프로그램의 CPU 사용을 제한 할 수 있습니다.

예를 들어 8 코어 호스트에서 단일 스레드 프로그램이 busy-wait (버그 또는 과부하로 인해) 발생하더라도 CPU 사용량은 12.5 %에 불과합니다. 1 개의 코어를 차지합니다. 이 최악의 경우 시스템은 여전히 ​​다른 서비스 프로세스에 사용할 수있는 컴퓨팅 리소스의 87.5 %를 보유하고 있습니다.

따라서 일부 보조 프로그램의 경우 기본 기능 프로세스와 동일한 시스템에서 실행해야하는 경우 (예 : 다른 서비스 프로세스의 상태를 모니터링해야 함) 단일 스레드로 만들면 시스템 컴퓨팅을 과도하게 잡는 것을 방지 할 수 있습니다. 자원.

프로세스 기반 분산 시스템 설계

"공통 모델"기사에서는 분산 시스템의 소프트웨어 설계 및 기능 분할이 일반적으로 "프로세스"를 기반으로해야한다고 언급했습니다. 전체 시스템이 하나의 프로세스로 구현되는 것이 아니라 기능이 분할 된 후 각 서비스 프로세스가 구현 될 때 필요에 따라 성능을 향상시키기 위해 멀티 스레딩을 사용할 수 있다는 점에서 멀티 스레딩 사용을 옹호합니다. 전체 분산 시스템에 대해 확장 가능, 즉 시스템 추가의 이점을 누릴 수 있습니다.

상위 수준 응용 프로그램의 경우 각 프로세스의 코드 양은 기성 라이브러리의 코드 양을 포함하지 않는 100,000 줄의 C ++ 미만으로 제어됩니다. 이런 식으로 각 과정을 하나의 두뇌로 완전히 이해할 수 있으며 혼란이 없습니다. (사실 50,000 줄을 말씀 드리고 싶습니다.)

다음은 Google의 " 분산 시스템 설계 소개 "의 좋은 기사입니다 . 마지막 터치는 실패를위한 설계 인 분산 시스템 설계입니다.

이 기사에서는 서비스 프로세스가 언제 멀티 스레딩을 사용해야하는지 계속 논의합니다. 먼저 단일 스레드의 장점에 대해 이야기하겠습니다.

단일 스레드 프로그램의 장점

  • 프로그래밍 관점에서 볼 때 단일 스레드 프로그램의 장점은 말할 것도없이 단순성입니다. 프로그램의 구조는 일반적으로 "공통 모델"에 명시된대로 IO 멀티플렉싱을 기반으로하는 이벤트 루프입니다. 또는 Yun Feng이 말했듯이 IO를 차단하는 직접 사용.
  • 단점 : 1. 예 발생 优先级反转, 2. 상대적으로 높은 지연 발생

이벤트 루프의 일반적인 코드 프레임 워크는 다음과 같습니다.

while (!done) {
    
    
 int retval = ::poll(fds, nfds, timeout_ms);
 if (retval < 0) {
    
    
  处理错误
 } else {
    
    
  处理到期的 timers
  if (retval > 0) {
    
    
   处理 IO 事件
  }
 }
}

이벤트 루프에는 명백한 단점이 있으며 선점 적이 지 않습니다. 이벤트 a의 우선 순위가 이벤트 b보다 높다고 가정하면 이벤트 a를 처리하는 데 1ms, 이벤트 b를 처리하는 데 10ms가 걸립니다. 이벤트 b가 a보다 먼저 발생하면 이벤트 a가 도착하면 프로그램은 poll () 호출을 떠나 이벤트 b 처리를 시작합니다. 이벤트 a는 처리 될 기회를 얻기 위해 10ms를 기다려야하며 총 응답 시간은 11ms입니다. 이것은 우선 순위 반전과 동일합니다.

이러한 단점은 멀티 스레딩으로 극복 할 수 있으며, 이는 멀티 스레딩의 주요 장점이기도합니다.

다중 스레드 프로그램에 성능상의 이점이 있습니까?

이전에 IO 바인딩이든 CPU 바인딩 서비스이든 멀티 스레딩은 절대적인 성능 이점이 없다고 말했습니다. 다음은이 문장의 의미에 대한 자세한 설명입니다.

이 문장은 적은 양의 CPU로드로 IO가 가득 차거나 적은 양의 IO 트래픽으로 CPU가 가득 찰 수 있다면 여러 스레드가 쓸모가 없다는 것을 의미합니다. 예를 들면 :

  1. 정적 웹 서버 또는 ftp 서버의 경우 CPU 부하가 비교적 적고 주요 병목 현상은 디스크 IO 및 네트워크 IO입니다. 이때 단일 스레드 프로그램 (모드 1)이 IO를 채울 수 있습니다. IO 하드웨어 용량이 포화 되었기 때문에 멀티 스레딩을 사용해도 처리량이 향상되지 않습니다. 마찬가지로이 시점에서 CPU 수를 늘려도 처리량을 향상시킬 수 없습니다.
  2. CPU가 꽉 차는 경우는 드물기 때문에 여기에서 예제를 작성해야합니다. 입력이 n 개의 정수인 서비스가 있다고 가정하고 합계가 0이되도록 m 개의 정수를 선택할 수 있는지 물어 봅니다 (여기서는 n <100, m> 0). 이것은 NP-Complete 인 유명한 부분 집합 합 문제입니다. 이러한 "서비스"의 경우 n의 작은 값도 CPU를 죽일 것입니다. 예를 들어 n = 30이면 하나의 입력은 120 바이트 (32 비트 정수)를 넘지 않으며 CPU 컴퓨팅 시간이 길어질 수 있습니다. 몇 분으로. 이러한 어플리케이션에는 Mode 3a가 가장 적합하며 멀티 코어를 활용할 수 있고 프로그램이 간단합니다.

즉, 어떤 당사자가 조기에 병목 현상에 도달하더라도 멀티 스레드 프로그램은 이점이 없습니다.

이것에 대해 말하자면, 일부 독자들은 이미 참을성이 없을 것입니다 : 당신은 너무 많이 이야기했고, 당신은 단일 스레딩의 이점에 대해 이야기하고 있습니다. 그렇게 많은 스레드를 사용하는 것은 무엇입니까?

다중 스레드 프로그램에 대한 시나리오

개선 响应速度, IO 및 "계산"이 서로 겹치도록하고 latency**를 입니다.

내 생각 엔 서로 겹쳐 IO와 "계산"수, 응답 속도를 향상, 대기 시간 감소 : 멀티 스레딩의 해당 시나리오가 있습니다 .

멀티 스레딩은 절대 성능을 향상시킬 수 없지만 평균 응답 성능을 향상시킬 수 있습니다.

다중 스레드가 필요한 프로그램은 일반적으로 다음을 충족해야합니다.

  • 사용 가능한 여러 CPU가 있습니다. 单核机器上多线程的优势不明显.
  • 线程间有共享数据. 공유 데이터가 없으면 모델 3b를 사용하십시오. 스레드간에 공유 된 데이터를 최소화해야하지만 이것이 없다는 의미는 아닙니다.
  • 共享的数据是可以修改的정적 상수 테이블 대신. 데이터를 수정할 수없는 경우 프로세스간에 공유 메모리를 사용할 수 있으며 모드 3이면 충분합니다.
  • 제공 비 균일 한 서비스를. 즉, 이벤트에 대한 응답은 우선 순위가 다르며 전용 스레드를 사용하여 우선 순위가 높은 이벤트를 처리 할 수 ​​있습니다. 우선 순위 반전을 방지합니다.
  • latency그리고 throughput똑같이 중요한, IO가 결합 또는 CPU 바운드 프로그램 간단한 논리는 아니다;
  • 비동기 작업을 활용합니다. 로깅과 같은. 로그 파일을 디스크에 기록하든 로그 서버에 메시지를 보내든 중요 경로를 차단해서는 안됩니다.
  • scale up(확장). 좋은 멀티 스레드 프로그램은 CPU 수를 늘리는 이점을 누릴 수 있어야합니다. 현재 주류는 8 코어이고 16 코어 머신이 곧 사용될 것입니다.
  • 예측 가능한 성능이 있습니다. 부하가 증가하면 성능이 서서히 감소하고 특정 임계점을 초과하면 급격히 떨어집니다. 스레드 수는 일반적으로로드에 따라 변경되지 않습니다.
  • 멀티 스레딩 有效地划分责任与功能은 각 스레드의 논리를 상대적으로 간단하고 단일 작업이며 코딩하기 쉽게 만듭니다. Win32 SDK 프로그램처럼 모든 로직을 이벤트 루프에 넣는 대신.

이러한 조건은 상대적으로 추상적이며 여기에 구체적인 (가상이지만) 예가 있습니다.

Linux 서버의 클러스터를 관리한다고 가정하면이 클러스터에는 8 개의 컴퓨팅 노드와 1 개의 제어 노드가 있습니다. 시스템의 구성은 동일한 듀얼 채널 쿼드 코어 CPU, 기가비트 네트워크 상호 연결입니다. 이제 간단한 차량 관리 소프트웨어 (LLNL의 SLURM 참조)를 작성해야합니다 .이 소프트웨어는 세 가지 프로그램으로 구성됩니다.

  • 제어 노드에서 실행되는 마스터 인이 프로그램은 전체 클러스터의 상태를 모니터링하고 제어합니다.
  • 각 컴퓨팅 노드에서 실행되는 슬레이브는 작업을 시작 및 종료하고 머신의 리소스를 모니터링합니다.
  • 작업을 제출하는 데 사용되는 최종 사용자 용 클라이언트 명령 줄 도구입니다.

이전 분석에 따르면 슬레이브는 "감시 프로세스"이며 다른 작업 프로세스를 시작하므로 단일 스레드 프로그램이어야합니다. 또한 단일 스레드 모델에도 적합한 CPU 리소스를 너무 많이 차지하지 않아야합니다.

마스터는 모드 2에서 다중 스레드 프로그램이어야합니다.

  • 8 코어 머신 만 차지하고 있으며 모델 1을 사용하면 CPU 자원의 87.5 %가 낭비됩니다.
  • 전체 클러스터의 상태는 메모리에 완전히 저장 될 수 있어야하며 이러한 상태는 공유되고 가변적입니다. 모드 3을 사용하면 프로세스 간의 상태 동기화가 큰 문제가됩니다. 그리고 공유 메모리가 많이 사용되면 다중 프로세스 클로킹을 사용하는 다중 스레드 프로그램에 해당합니다.
  • 마스터의 주요 성능 지표는 처리량이 아니라 대기 시간으로, 다양한 이벤트에 가능한 한 빨리 응답한다는 의미입니다. 거의 IO 또는 CPU가 부족하지 않습니다.
  • 마스터가 모니터링하는 이벤트는 우선 순위가 다릅니다. 프로그램의 정상 작동 종료 및 비정상 충돌의 처리 우선 순위가 다르며 컴퓨팅 노드의 디스크가 가득 찬 두 경보 조건의 우선 순위와 섀시 온도도 다릅니다. 단일 스레드를 사용하면 우선 순위 반전이 발생할 수 있습니다.
  • 마스터와 각 슬레이브 사이에 TCP 연결이 있다고 가정하면 마스터는 2 개 또는 4 개의 IO 스레드를 사용하여 8 개의 TCP 연결을 처리하여 지연을 효과적으로 줄일 수 있습니다.
  • 마스터는 로그를 로컬 하드 디스크에 비동기 적으로 기록하므로 로깅 라이브러리에 자체 IO 스레드가 있어야합니다.
  • 마스터는 데이터베이스를 읽고 쓰기를 원할 수 있으며 데이터베이스가 연결되는 타사 라이브러리는 자체 스레드를 갖고 마스터의 코드를 콜백 할 수 있습니다.
  • 마스터는 여러 클라이언트에 서비스를 제공해야하며 멀티 스레딩을 사용하면 클라이언트 응답 시간도 줄일 수 있습니다. 즉, 2 개의 IO 스레드를 사용하여 클라이언트와의 통신을 처리 할 수 ​​있습니다.
  • 마스터는 또한 사용자가 적극적으로 폴링 (폴링) 할 필요가 없도록 클러스터 상태를 브로드 캐스트 (푸시)하는 모니터 인터페이스를 제공 할 수 있습니다. 이 함수를 별도의 스레드로 수행하면 구현하기가 더 쉽고 다른 주요 기능을 엉망으로 만들지 않습니다.
  • 마스터는 총 10 개의 스레드를 열었습니다.
    • 슬레이브와 통신하는 데 사용되는 4 개의 IO 스레드
    • 로깅 스레드 1 개
    • 데이터베이스 IO 스레드 1 개
    • 클라이언트와 통신하는 2 개의 IO 스레드
    • 작업 스케줄링과 같은 백그라운드 작업을 수행하는 데 사용되는 1 개의 주 스레드
    • 함대의 상태를 적극적으로 브로드 캐스트하는 데 사용되는 푸시 스레드 1 개
  • 스레드 수는 코어 수보다 약간 많지만 이러한 스레드는 종종 유휴 상태이므로 제어 가능한 지연을 보장하기 위해 OS의 프로세스 스케줄링에 의존 할 수 있습니다.

요약하면 마스터가 다중 스레드 방식으로 작성되는 것이 자연스럽고 효율적입니다.

스레드 분류

내 경험에 따르면 다중 스레드 서비스 프로그램의 스레드는 크게 세 가지 범주로 나눌 수 있습니다.

  1. IO 스레드, 이러한 종류의 스레드의 메인 루프는 io 멀티플렉싱이며 select / poll / epoll 시스템 호출을 대기합니다. 이 유형의 스레드는 시간이 지정된 이벤트도 처리합니다. 물론 그 기능은 IO뿐만 아니라 일부 계산도 포함될 수 있습니다.
  2. 컴퓨팅 스레드, 이러한 종류의 스레드의 주 루프는 조건 변수를 기다리는 차단 대기열입니다. 이러한 스레드는 일반적으로 스레드 풀에 있습니다.
  3. 로깅 및 데이터베이스 연결과 같은 타사 라이브러리에서 사용하는 스레드입니다.

서버 프로그램은 일반적으로 스레드를 자주 시작하고 종료하지 않습니다. 심지어 내가 작성한 프로그램에서 create thread는 프로그램이 시작될 때만 호출되고 서비스 실행 중에는 호출되지 않습니다.

멀티 코어 시대에 멀티 스레드 프로그래밍은 불가피하며 "타조 알고리즘"은 답이 아닙니다.

추천

출처blog.csdn.net/qq_22473333/article/details/113515020