muduo 라이브러리 학습의 공통 프로그래밍 모델 04-공통 프로그래밍 모델 및 프로세스 간 통신 모드 선택

동양의 연구 노트

원본 링크 : https://blog.csdn.net/yuyin86/article/details/7086424

1. 단일 스레드 서버에 일반적으로 사용되는 프로그래밍 모델

일반적으로 사용되는 단일 스레드 프로그래밍 모델은 https://blog.csdn.net/qq_22473333/article/details/112910686을 참조 하십시오.

고성능 네트워크 프로그램 중에서 가장 널리 사용되는 모델은 아마도 " non-blocking IO + IO multiplexing "모델, 즉 Reactor 모드 일 것입니다.

l lighttpd, 단일 스레드 서버. (Nginx는 유사한 것으로 추정됩니다.)

  • libevent / libev

  • ACE, Poco C ++ 라이브러리 (QT 보류 중)

  • Java NIO (Selector / SelectableChannel), Apache Mina, Netty (Java)

  • POE (Perl)

  • Twisted (Python)

반대로 boost :: asio 및 Windows I / O Completion Ports는 Proactor 모드를 구현하며 응용 프로그램 영역이 좁아 보입니다. 물론 ACE는 표시되지 않은 Proactor 모드도 구현합니다.

"비 차단 IO + IO 멀티플렉싱"모델에서 프로그램의 기본 구조는 이벤트 루프입니다. (코드는 설명 용이며 다양한 상황을 완전히 고려하지 않습니다.)

while (!done)

{
    
    
  int timeout_ms = max(1000, getNextTimedCallback());
  int retval = ::poll(fds, nfds, timeout_ms);
  if (retval < 0) {
    
    
    // 处理错误

  } else {
    
    
    // 处理到期的 timers
    if (retval > 0) {
    
    
    // 处理 IO 事件
    }
  }
}

물론 select (2) / poll (2)에는 많은 단점이 있습니다 .Linux는 epoll으로 대체 할 수 있으며 다른 운영 체제에도 상응하는 고성능 대안이 있습니다 (c10k 문제 검색).

Reactor모델의 장점은 분명하고 프로그래밍이 간단하며 효율성이 좋습니다 . 네트워크 읽기 및 쓰기를 사용할 수있을뿐만 아니라 연결 설정 (연결 / 수락) 및 DNS 확인도 비 차단 방식으로 수행하여 동시성과 처리량을 향상시킬 수 있습니다. IO 집약적 인 애플리케이션에 적합합니다. Lighttpd즉, 내부 fdevent 구조가 매우 섬세하고 학습 할 가치가 있습니다. (여기서는 IO 차단의 차선책은 고려하지 않습니다.)

물론 고품질의 Reactor를 구현하는 것은 그리 쉽지 않으며 오픈 소스 라이브러리를 사용하지 않았으므로 여기서는 권장하지 않습니다.

2. 일반적인 다중 스레드 서버의 스레딩 모델

이와 관련하여 내가 찾을 수있는 문서는 많지 않으며 아마도 거의 없을 것입니다.

  1. 각 요청은 차단 IO 작업을 사용하여 스레드를 만듭니다. Java 1.4가 NIO를 도입하기 전에는 이것이 Java 네트워크 프로그래밍에 권장되는 방법이었습니다. 불행히도 확장 성은 좋지 않습니다.

  2. 스레드 풀을 사용하고 차단 IO 작업도 사용하십시오. 1과 비교하여 성능 향상을위한 조치입니다.

  3. 사용 비 차단 IO + IO 멀티플렉싱 . 즉, Java NIO의 방식입니다.

  4. 리더 / 팔로어 및 기타 고급 모드

기본적으로 세 번째 비 차단 IO + 스레드 모드 당 하나의 루프를 사용합니다.

http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod#THREADS_AND_COROUTINES

스레드 당 하나의 루프

이 모델에서 프로그램의 각 IO 스레드에는 event loop읽기, 쓰기 및 타이밍 이벤트를 처리하는 데 사용되는 하나 (또는 Reactor라고 함)가 있습니다 (주기적 또는 단일 시간에 관계없이) 코드 프레임 워크는 섹션 2와 동일합니다.

이 접근 방식의 장점은 다음과 같습니다.

  1. 쓰레드 수는 기본적으로 고정되어 있으며 프로그램 시작시 설정할 수 있으며 자주 생성 및 파괴되지 않습니다.

  2. 스레드간에로드를 쉽게 할당 할 수 있습니다.

event loop 스레드의 메인 루프를 나타내며 어떤 스레드가 작업을 수행하도록해야 할 경우 해당 스레드의 루프에 타이머 또는 IO 채널 (TCP 연결)을 등록하면됩니다.

  • 실시간 성능이 필요한 연결은 단일 스레드를 사용할 수 있습니다.
  • 많은 양의 데이터와의 연결은 스레드를 독점 할 수 있습니다.
  • 데이터 처리 작업을 다른 스레드에 할당합니다.
  • 다른 보조 보조 연결은 스레드를 공유 할 수 있습니다.

사소하지 않은 서버 프로그램의 경우 일반적으로 non-blocking IO + IO 멀티플렉싱이 사용됩니다 . 각 연결 / 수락자는 Reactor에 등록됩니다. 프로그램에는 여러 Reactor가 있으며 각 스레드에는 최대 하나의 Reactor가 있습니다.

다중 스레드 프로그램은 Reactor, 즉 线程安全. 한 스레드가 다른 스레드의 루프에 물건을 넣을 수 있도록하려면 루프가 스레드로부터 안전해야합니다.

스레드 풀

그러나 IO 스레드에는 가벼운 컴퓨팅 작업이 없으며 낭비되는 이벤트 루프 비트를 사용 blocking queue합니다. 작업 대기열 구현 (TaskQueue) 을 사용하는 보완 프로그램이 있습니다 .

blocking_queue<boost::function<void()> > taskQueue;  // 线程安全的阻塞队列
void worker_thread()

{
    
    
  while (!quit) {
    
    
    boost::function<void()> task = taskQueue.take();  // this blocks
    task();  // 在产品代码中需要考虑异常处理
  }
}

이러한 방식으로 스레드 풀을 구현하는 것은 특히 쉽습니다.

용량이 N 인 스레드 풀을 시작합니다.

int N = num_of_computing_threads;

for (int i = 0; i < N; ++i) {
    
    
  create_thread(&worker_thread);  // 伪代码:启动线程
}

또한 사용하기 매우 간단합니다.

boost::function<void()> task = boost::bind(&Foo::calc, this);
taskQueue.post(task);

위의 12 줄 정도의 코드는 Java 5의 ThreadPoolExecutor의 특정 "구성"과 거의 동일한 고정 된 수의 스레드 풀을 구현합니다. 물론 실제 프로젝트에서 이러한 코드는 전역 개체를 사용하는 대신 클래스에 캡슐화되어야합니다.

주목해야 할 또 다른 사항 : Foo 객체의 수명, 저의 다른 블로그 "When the destructor encounters multithreading-Thread-safe object callbacks in C ++"는이 문제에 대해 자세히 설명합니다.
http://blog.csdn.net / Solstice / archive / 2010 /01/22/5238671.aspx

작업 대기열 외에도 blocking_queue<T>소비자를 사용 하여 데이터를 얻을 수 있습니다. 생산자 대기열, 즉 T는 함수 대신 객체의 데이터 유형이며 데이터를 가져올 대기열 소비자 (들)가 처리됩니다. 이것은 작업 대기열보다 더 구체적입니다.

blocking_queue는 다중 스레드 프로그래밍을위한 강력한 도구입니다. 구현은 Java 5 util.concurrent에서 (Array | Linked) BlockingQueue를 참조 할 수 있습니다. 일반적으로 C ++에서는 deque를 기본 컨테이너로 사용할 수 있습니다. Java 5의 코드는 가독성이 높고 코드의 기본 구조는 교과서 (뮤텍스 1 개, 조건 변수 2 개)와 동일하며 견고성이 훨씬 높습니다. 직접 구현하고 싶지 않다면 기성 라이브러리를 사용하는 것이 좋습니다. (혼돈이 권장되지 않는 무료 라이브러리를 사용하지 않았으므로 관심있는 학생들은 Intel Threading Building Blocksconcurrent_queue에서 시도해 볼 수 있습니다 .)

유도

요약하자면 내가 권장하는 다중 스레드 서버 프로그래밍 모델은 다음과 같습니다. event loop per thread+ thread pool.

  • 이벤트 루프는 비 차단 IO 및 타이머로 사용됩니다.
  • 스레드 풀은 계산에 사용되며 작업 대기열 또는 소비자-생산자 대기열이 될 수 있습니다.

이런 식으로 서버 프로그램을 작성하려면 Reactor 모델을 기반으로 한 고품질 네트워크 라이브러리가 필요합니다. 저는 사내 제품 만 사용했습니다. 시장에서 일반적인 C ++ 네트워크 라이브러리를 비교하거나 추천 할 수 없습니다. 죄송합니다.

프로그램에서 사용되는 루프, 쓰레드 풀의 크기 등 프로그램에 사용되는 특정 파라미터는 애플리케이션에 따라 설정해야합니다. 기본 원칙은 CPU와 IO가 모두 효율적으로 작동 할 수 있도록 "임피던스 매칭"입니다. 나중에 고려할 사항.

여기에서는 스레드 종료에 대한 이야기가 없으며 다음 블로그 "Multithreaded Programming Anti-pattern"에서 논의합니다.

또한 logging프로그램 에서 특별한 작업을 수행하는 개별 스레드가있을 수 있습니다 . 예를 들어 기본적으로 응용 프로그램에는 보이지 않지만 시스템 용량이 과대 평가되지 않도록 리소스 (CPU 및 IO)를 할당 할 때 포함되어야합니다.

3. 프로세스 간 통신 및 스레드 간 통신

Linux에서 프로세스 간 통신 (IPC)에는 무수한 방법이 있습니다 .UNPv2는 소켓은 말할 것도없고 파이프, FIFO, POSIX 메시지 큐, 공유 메모리, 신호 등을 나열합니다. 뮤텍스, 조건 변수, 판독기-작성기 잠금, 레코드 잠금, 세마포어 등과 같은 많은 동기화 기본 요소도 있습니다.

선택하는 방법? 개인적인 경험에 따르면 그 소중함은 너무 비싸지 않습니다 .3 ~ 4 가지를 신중하게 선택하면 제 업무 요구 사항을 충분히 충족시킬 수 있고, 아주 잘 사용할 수 있고 실수하기 쉽지 않습니다.

프로세스 간 통신

프로세스 간 통신을 위해 소켓을 선호합니다 (주로 TCP를 언급하고 UDP를 사용하지 않았으며 Unix 도메인 프로토콜을 고려하지 않았습니다). 가장 큰 장점은 크로스 호스트가 가능하고 확장 성이 있다는 것입니다. 어쨌든 여러 개의 프로세스가 있는데 하나의 기계가 처리 능력이 부족하면 여러 기계를 사용하여 처리하는 것이 당연합니다. 동일한 LAN의 여러 시스템에 프로세스를 배포하고 host : port 구성을 변경하여 프로그램을 계속 사용합니다. 반대로 위에 나열된 다른 IPC는 시스템을 교차 할 수 없습니다 (예 : 공유 메모리가 가장 효율적이지만 아무리 두 시스템의 메모리를 효율적으로 공유 할 수 없음). 이는 확장 성을 제한합니다.

프로그래밍에서 TCP 소켓과 파이프는 모두 바이트 스트림을 보내고받는 데 사용되는 파일 설명자이며 둘 다 읽기 / 쓰기 / fcntl / select / poll 등을 할 수 있습니다. 차이점은 TCP는 양방향이고, 파이프는 단방향 (Linux)이며, 프로세스 간 양방향 통신을 위해 두 개의 파일 디스크립터를 열어야하므로 불편합니다. 파이프를 사용하려면 프로세스가 부모-자식 관계를 가져야합니다. , 파이프 사용을 제한합니다. 바이트 스트림을 송수신하는 통신 모델에서 소켓 / TCP보다 자연스러운 IPC는 없습니다. 물론 pipe에는 Reactor / Selector를 작성할 때 select (또는 동등한 poll / epoll) 호출을 비동기 적으로 깨우는 데 사용되는 고전적인 애플리케이션 시나리오도 있습니다 (Sun JVM은 Linux에서이 작업을 수행합니다).

TCP 포트는 프로세스가 독점적으로 소유하며 운영 체제는 자동으로이를 회수합니다 (설정된 연결의 수신 대기 포트와 TCP 소켓은 모두 파일 설명자이며 프로세스가 종료되면 운영 체제가 모든 파일 설명자를 닫습니다). 이는 프로그램이 예기치 않게 종료 되더라도 시스템에 가비지가 남지 않음을 보여줍니다. 프로그램을 다시 시작한 후 운영 체제를 다시 시작하지 않고도 비교적 쉽게 복구 할 수 있습니다 (크로스 프로세스 뮤텍스 사용 위험). 또 다른 장점은 포트가 배타적이므로 프로그램이 다시 시작되는 것을 막아 (후자의 프로세스는 포트를 잡을 수 없으며 당연히 작동하지 않음) 예기치 않은 결과를 초래할 수 있습니다.

두 프로세스가 TCP를 통해 통신합니다. 하나가 충돌하면 운영 체제가 연결을 닫아 다른 프로세스가 거의 즉시 연결을 인식하고 신속하게 장애 조치를 수행 할 수 있습니다. 물론 애플리케이션 레이어의 하트 비트도 필수 불가결하며, 향후 서버의 날짜와 시간 처리에 대해 이야기 할 때 하트 비트 프로토콜의 디자인에 대해 이야기하겠습니다.

다른 IPC와 비교할 때 TCP 프로토콜의 자연스러운 이점은 可记录可重现tcpdump / Wireshark가 두 프로세스 간의 프로토콜 / 상태 분쟁을 해결하는 데 좋은 도우미라는 것입니다.

또한 네트워크 라이브러리에 "연결 재시도"기능이있는 경우 시스템의 프로세스를 특정 순서로 시작하도록 요구할 수 없으며 모든 프로세스를 개별적으로 다시 시작할 수 있으므로 개발에 큰 의미가 있습니다. 안정적인 분산 시스템.

TCP 바이트 스트림 통신을 사용하면 마샬링 / 언 마샬링 오버 헤드가 발생하므로 정확한 메시지 형식을 정확하게 와이어 형식으로 선택해야합니다. 이것은 내 다음 블로그의 주제가 될 것이며 지금은 그것을 추천합니다 Google Protocol Buffers.

어떤 사람은 두 프로세스가 같은 컴퓨터에 있으면 공유 메모리를 사용하거나 TCP를 사용한다고 말할 수 있습니다. 예를 들어 MS SQL Server는 두 가지 통신 방법을 동시에 지원합니다. 나는 약간의 성능 향상을 위해 코드의 복잡성을 크게 증가시키는 것이 가치가 있는지 물었습니다. TCP는 순차적으로 만 읽을 수 있고 쓰기 버퍼가있는 바이트 스트림 프로토콜입니다. 공유 메모리는 메시지 프로토콜입니다. 프로세스 a는 기본적으로 "대기 중지"방법 인 프로세스 b가 읽을 수 있도록 메모리 블록을 채 웁니다. 이 두 가지 방법을 하나의 프로그램으로 결합하려면 두 개의 IPC를 캡슐화하는 추상화 계층을 구축해야합니다. 이것은 불투명도를 가져오고 테스트의 복잡성을 증가시킬 것이며 통신의 한 당사자가 충돌하는 경우 상태 조정이 소켓보다 더 문제가 될 것입니다. 내가 가져 가지 않았습니다. 게다가 수만 달러에 구입 한 SQL Server 용 프로그램과 컴퓨터 리소스를 공유 할 의향이 있습니까? 제품의 데이터베이스 서버는 종종 독립적 인 높은 구성 서버이며 일반적으로 다른 리소스 집약적 인 프로그램을 동시에 실행하지 않습니다.

TCP 자체는 데이터 스트림 프로토콜입니다. 직접 통신에 사용하는 것 외에도 RPC / REST / SOAP와 같은 상위 계층 통신 프로토콜을 구축 할 수도 있습니다. 이는이 문서의 범위를 벗어납니다. 또한 지점 간 통신 외에도 응용 프로그램 수준의 브로드 캐스트 프로토콜도 매우 유용하여 상당히 제어 가능한 분산 시스템을 쉽게 구축 할 수 있습니다.

추천

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