c++ - 예제 - std::thread 종료




스레드 내에서 포크하는 것이 안전합니까? (5)

Dawn of Time으로 돌아가서, 우리는 쓰레드를 "경량 프로세스"라고 불렀습니다. 왜냐하면 프로세스가 많은 프로세스처럼 작동하지만 동일하지 않기 때문입니다. 가장 큰 차이점은 정의에 의한 스레드가 한 프로세스의 동일한 주소 공간에 있다는 점입니다. 이점은 스레드에서 스레드로 전환하는 속도가 빠르며, 본질적으로 메모리를 공유하므로 스레드 간 통신이 빠르며 스레드 생성 및 삭제가 빠릅니다.

이 구분은 완전한 주소 공간 인 "중량 프로세스"와 다릅니다. fork (2)에 의해 새로운 중량 프로그램이 생성됩니다. 가상 메모리가 유닉스 세계로 유입되면서 vfork (2) 와 다른 것들이 추가되었습니다.

fork (2) 는 모든 레지스터를 포함하여 프로세스의 전체 주소 공간을 복사하고 해당 프로세스를 운영 체제 스케줄러의 제어하에 둔다. 다음 번에 스케줄러가 돌아 오면 명령 카운터가 다음 명령어에서 선택됩니다. 분기 된 하위 프로세스는 상위의 복제본입니다. (쉘을 작성하고 있기 때문에 다른 프로그램을 실행하고 싶다면 exec (2) 호출로 포크를 따라 가면 새로운 주소 공간을 새 프로그램으로로드하고 복제 된 프로그램을 대체합니다.)

기본적으로 답은 그 설명에 묻혀 있습니다. 많은 LWP 스레드가있는 프로세스가 있고 프로세스를 포크하면 동시에 실행되는 많은 스레드가있는 두 개의 독립 프로세스가 생깁니다.

이 트릭은 심지어 유용합니다 : 많은 프로그램에서 많은 스레드를 가질 수있는 부모 프로세스가 있으며, 그 중 일부는 새로운 하위 프로세스를 포크합니다. (예를 들어, HTTP 서버가 그렇게 할 수 있습니다 : 포트 80에 대한 각각의 연결은 쓰레드에 의해 처리되고, CGI 프로그램과 같은 자식 프로세스는 fork 될 수 있습니다; exec (2) 는 CGI 프로그램을 실행하기 위해 호출됩니다 부모 프로세스 닫기 대신.)

내가 설명하자면 : 나는 이미 리눅스에서 외부 바이너리를 포크하고 실행하고 그것을 끝내기를 기다리는 애플리케이션을 개발했다. 결과는 fork + 프로세스에 고유 한 shm 파일에 의해 전달됩니다. 전체 코드는 클래스 내에 캡슐화됩니다.

이제는 속도를 높이기 위해 프로세스 스레딩을 고려하고 있습니다. 클래스 함수의 여러 인스턴스가 서로 다른 매개 변수를 사용하여 동시에 바이너리를 fork하고 실행하고 고유 한 shm 파일과 결과를 전달합니다.

이 스레드는 안전한가요? 안전함과는 별도로 스레드 내에서 포크하는 경우, 내가 볼 필요가있는 부분이 있습니까? 어떤 조언이나 도움을 많이 주시면 감사하겠습니다!


fork와 exec 사이의 코드에 매우 주의하는 한 다중 스레드 프로그램에서 fork하는 것이 안전합니다. 이 범위에서 재진입 (비동기식이라고도 함) 시스템 호출 만 만들 수 있습니다. 이론 상으로는 malloc이나 free가 허용되지 않습니다. 실제로는 기본 Linux 할당자가 안전하고 Linux 라이브러리가 의존합니다. 최종 결과는 기본 할당자를 사용해야한다는 것입니다.


여러분의 프로그램에 리눅스의 NPTL pthreads(7) 지원을 사용할 있지만 fork(2) 질문을 통해 발견 한 것처럼 스레드는 Unix 시스템에 어색하게 적합합니다.

fork(2) 는 최신 시스템에서 매우 저렴한 연산이므로 수행해야 할 핸들링이 많을 때 프로세스를 fork(2) 하는 것이 더 좋습니다. 공유 데이터 버그를 줄이는데 좋은 것은 아니지만 프로세스간에 데이터를 이동 하거나 공유 메모리 를 사용하는 파이프를 만들어야 한다는 것을 의미합니다. shmget(2) 또는 shm_open(3) ).

그러나 스레딩을 사용하도록 선택하면 fork(2) 맨 페이지의 다음 힌트와 함께 새 프로세스를 fork(2)수 있습니다 .

   *  The child process is created with a single thread  the
      one that called fork().  The entire virtual address space
      of the parent is replicated in the child, including the
      states of mutexes, condition variables, and other pthreads
      objects; the use of pthread_atfork(3) may be helpful for
      dealing with problems that this can cause.

유닉스의 'fork ()'시스템 콜을 사용하고 있다면 프로세스를 사용하고있는 쓰레드를 기술적으로 사용하지 않고있다. 그들은 자신의 메모리 공간을 가질 것이기 때문에 서로 간섭 할 수 없다.

각 프로세스가 서로 다른 파일을 사용하는 한 아무런 문제가 없어야합니다.


문제는 fork ()가 호출하는 스레드 만 복사하고 자식 스레드에 보관 된 뮤텍스는 분기 된 자식에서 영원히 잠길 것이라는 점입니다. pthread 솔루션은 pthread_atfork() 핸들러입니다. 아이디어는 3 개의 핸들러를 등록 할 수 있다는 것입니다 : 하나의 프리 포크, 하나의 부모 핸들러 및 하나의 자식 핸들러. fork() 가 발생하면 prefork는 fork 이전에 호출되고 모든 응용 프로그램 뮤텍스를 가져올 것으로 예상됩니다. 부모와 자식 모두 부모 프로세스와 자식 프로세스에서 모든 뮤텍스를 해제해야합니다.

이것은 이야기의 끝이 아니다! 라이브러리는 pthread_atfork 를 호출하여 라이브러리 특정 뮤텍스에 대한 핸들러를 등록합니다. 예를 들어 Libc가이를 수행합니다. 응용 프로그램은 제 3 자 라이브러리가 보유한 뮤텍스 (mutex)를 알 수 없기 때문에 각 라이브러리는 pthread_atfork 를 호출하여 fork() 가 발생했을 때 자체 뮤텍스가 정리되었는지 확인해야합니다.

문제는 pthread_atfork 핸들러가 관련없는 라이브러리에 대해 호출되는 순서가 정의되지 않는다는 것입니다 (라이브러리가 프로그램에 의해로드되는 순서에 따라 다름). 이것은 기술적으로 교착 상태 때문에 prefork 처리기 내부에서 교착 상태가 발생할 수 있음을 의미합니다.

예를 들어, 다음 순서를 고려하십시오.

  1. 스레드 T1 호출 fork()
  2. T1에서 얻은 libc 용 prefork 핸들러
  3. 다음으로 스레드 T2에서 타사 라이브러리 A는 자체 뮤텍스 AM을 획득 한 다음 뮤텍스가 필요한 libc 호출을 작성합니다. libc 뮤텍스가 T1에 의해 유지되기 때문에 이것은 차단됩니다.
  4. 스레드 T1은 라이브러리 A에 대한 프리 fork 핸들러를 실행합니다.이 핸들러는 T2에 의해 보유 된 AM을 확보하기 위해 대기하는 것을 차단합니다.

교착 상태가 있으며 자신의 뮤텍스 나 코드와 관련이 없습니다.

이것은 한 번 작업 한 프로젝트에서 실제로 발생했습니다. 그 당시 내가 찾은 조언은 포크 나 쓰레드 중 하나를 선택하는 것이지만 둘 다를 선택하는 것이 아닙니다. 그러나 일부 응용 프로그램의 경우 실용적이지 않을 수 있습니다.





fork