이 기사에서는 특정 상황에서 이미 실행 중인 스레드에 중지 또는 취소 신호를 보낼 수 있는 C++20의 std::jthread의 최신 기능을 살펴본다. cpp 참조에서 첫 번째 줄을 다시 인용합니다.
아티클을 읽을 수 있도록 std::를 생략하겠습니다. 그리고 라이브러리 구조의 모든 발생은 별도로 명시되지 않는 한 C++의 std 네임스페이스에 속한다는 것을 의미합니다.
1. 소개: 스레드를 공동으로 중지하는 두 가지 방법
jthread는 실행 스레드를 중지하기 위한 협력 수단을 제공하며, 이는 스레드가 중단되거나 killed²가 될 수 없으며 중지하라는 신호만 표시될 수 있음을 의미한다. 스레드에는 두 가지 방법이 있으며 두 방법 모두 std:stop_source 유형의 공유 중지 상태를 사용한다.
공유 정지 상태 (stop_source )와 std::stop_token의 도움으로 정지 요청이 만들어졌는지 여부를 확인할 수 있다. 만약 그것이 만들어졌다면, 스레드가 실행하는 메소드는 즉시 반환될 수 있고, 중지 요청에 대한 구현을 제공하는 책임은 프로그래머에게 있다. 그러나 stop_token이 정지 신호를 보낼 수 있는 메커니즘은 다양할 수 있으며 크게 두 가지 범주로 분류할 수 있다.
- jthread의 내부 공유 중지 상태에 의존하여.
- 외부 공유 정지 상태를 사용하여.
이 문서에서는 jthread의 내부 공유 중지 상태에 의존하여 jthread:request_stop 또는 jthread를 사용하여 실행 중인 jthread에 대해 중지/취소 신호를 보내는 방법을 설명합니다.:~jthread(또는 jthread의 소멸자) 따라서 이 문서에서는 jthread 에 의해 내부적으로 관리되므로 std::stop_source에 대해 설명하지 않겠다.
2. std::jthread 내부 정지 상태 사용
cpp 참조 페이지의 두 번째 단락의 첫 번째 줄은 jthread의 내부 정지 상태에 대해 다음과 같이 말한다.
함수 형태의 작업이 jthread의 생성자에게 전달될 때, 내부 공유 중지-sate의 중지 토큰(std::stop_token)은 함수가 stop_token을 첫 번째 매개 변수로 받아들이면 함수로 전달된다. 그러므로 jthread::request_stop()에 반응하기 위해서는 jthread가 실행하는 함수가 첫 번째 파라미터를 stop_token 로 받아들여야 한다. 이것은 정확히 cpp 참조 페이지에서 jthread에 할당되는 작업에 대해 말하는 것입니다.
2.1 함수의 첫 번째 파라미터로서 std::stop_token이 없는 첫 번째 예
jthread::request_stop 메서드가 실행 중이지만 thread t1이 완료되기 전에 메서드 request_stop이 호출되더라도 스레드가 멈추지 않는 다음 코드를 생각해 보십시오.
#include <iostream>
#include <thread>
#include <syncstream>
void foo(){
std::osyncstream syncout{std::cout};
//execute for approx 6 seconds
for(int secs=0; secs<6; ++secs){
std::this_thread::sleep_for(1s);
syncout <<"from foo: " << secs+1
<< " seconds elapsed\n"
<< std::flush_emit;
}
}
void stop_foo(){
std::jthread t1{foo}; //1:
std::this_thread::sleep_for(2s); //2:
for(int secs=0; secs<2; ++secs){
std::this_thread ::sleep_for(1s);
std::cout << "stop_foo ran for << secs+1 << " seconds\n";
}
t1.request_stop(); //3:
}
int main(){stop_foo();}
위의 코드 조각에서 stop_foo 함수는 stop_foo 함수에서 1, 2, 3으로 표시된 다음 점을 주목해야 한다.
1: 먼저 약 6초간 실행될 수 있는 나사산 t1에서 작업 푸가 시작됩니다.
2: 다음으로 주 나사산을 2초간 정지시키고 2초간 더 작동합니다.
3: 마지막으로 실행 중인 동안 t1이 정지하라는 신호가 표시됩니다(약 4초 후).
콘솔의 출력은 다음과 같습니다.
from foo: 1 seconds elapsed
from foo: 2 seconds elapsed
from stop_foo: 1 seconds elapsed
from foo: 3 seconds elapsed
from stop_foo: 2 seconds elapsed
from foo: 4 seconds elapsed
from foo: 5 seconds elapsed
from foo: 6 seconds elapsed
함수 foo가 stop_token을 첫 번째 매개 변수로 사용하지 않기 때문에 t1.request_stop()에 대한 호출은 t1에서 실행되는 foo에 영향을 미치지 않기 때문에 예상된다.
2.2 std:stop_token을 함수의 첫 번째 파라미터로 사용한 첫 번째 예제
이제 같은 함수를 가지지만 약간 수정해 보겠습니다.
- foo 함수에 대한 첫 번째 매개 변수로 stop_messages를 전달합니다.
- 정지 토큰에서 정지 신호가 요청되었는지 쿼리하도록 foo 본문을 수정합니다.
코드의 수정된 부분은 굵게 표시되며 새 수정사항으로 출력되는 내용은 다음과 같습니다.
void foo(std::stop_token token){
...
for(int secs=0; secs<6; ++secs){
...
//query from time to time if stop has been requested
if (token.stop_requested()) {
std::cout << "foo requested to stop\n";
return;
}
}
}
output
------
from foo: 1 seconds elapsed
from foo: 2 seconds elapsed
from stop_foo: 1 seconds elapsed
from foo: 3 seconds elapsed
from stop_foo: 2 seconds elapsed
from foo: 4 seconds elapsed
foo requested to stop
출력에서 foo는 초기 6카운트 대신 4카운트만 인쇄했으며 마지막 인쇄는 메인 스레드에서 약 4초 후에 트리거된 if -condition 내에서 이루어졌습니다.
foo의 새로운 수정으로 메인 스레드는 stop_tokenin thread t1로 신호를 보낼 수 있고, foo에서 조건이 foo, 또는 다시 말해서 foo로 반환되는지의 본체는 협력하고 일찍 반환된다. 작업 예는 컴파일러 탐색기의 이 링크에서 액세스할 수 있습니다.
2.3 jthread 소멸자의 실행 결과 정지 신호
스레드 정지 신호를 수동으로 보내는 것 외에도, 강력한 RAII는 스레드 소멸기에 구현되어 스레드가 조인할 수 있는 경우 소멸자가 정지 신호를 함수에 전송한다.
foo의 경우 약 6초 동안 실행하도록 프로그래밍되어 있지만 funciton stop_foo 내에서 foo를 호출하는 스레드는 약 4초 동안만 "alive"된다. 따라서 t1.request_stop()이 실행되지 않더라도 t1은 ~4초 후에 스코프를 벗어나 소멸자가 실행되어 foo가 조기에 중지됩니다. stop_foo에 대한 수정된 코드는 t1.reques_stop이 단순히 주석 처리되는 아래에 있습니다.
void stop_foo(){
...
for(int secs=0; secs<2; ++secs){
...
...
}
//t1.request_stop();
//t1 goes out of scope after ~ 4 seconds
// if(t1.joinable()) then t1.~jthread sends a stop signal to foo
}
그리고 출력은 섹션 2.2와 동일하게 유지됩니다. 마지막 예는 컴파일러 탐색기에서 볼 수 있다.
2.4 함수 내 stop_token에 대한 stop_callback 부착
cpp 참조에 따르면 std::stop_callback은 함수를 실행하는 스레드가 request_stop(섹션 2.2 참조)을 수신하거나 스코프를 벗어날 때마다(섹션 2.3 참조) 함수 내에 콜백을 등록하는 RAII 객체 유형입니다. 중지 칼라백은 어디서나 등록할 수 있지만 동일한 작업 스레드의 공유 중지 상태와 연결되어야 합니다. 중지 콜백은 명시적으로 request_stop을 호출하는 스레드에서 호출되거나, 중지가 아직 요청되지 않은 경우 jthread의 파괴로 인해 호출된다.
foo 함수(비동기적으로 시작되는 작업) 내에 stop_callback을 등록함으로써 이것이 어떻게 동작하는지 간략히 살펴보겠습니다.
void foo(std::stop_token token){
...
//register stop callback
std::stop_callback cb{token, [&syncout]{
syncout << "stop callback executed from
<< std::this_thread::get_id()
<< "\n"<<std::flush_emit
};
for(int secs=0; secs<6; ++secs){
...
}
}
void stop_foo(){
std::osyncstream syncout{std::cout};
//get the thread id of stop_foo function
syncout << "stop foo in thread: "
<< std::this_thread::get_id()
<< "\n" << std::flush_emit;
...
for(int secs=0; secs<6; ++secs){...}
//launch another thead and request stop from there
std::jthread stop_req_thread{
[&t1, &syncout]{
syncout << "stop_req_thread thread id: "
<< std::this_thread::get_id()
<< "\n" << std::flush_emit;
t1.request_stop();
};
int main(){ stop_foo();}
```
위의 코드 조각에서 콜백은 foo 함수 내에 등록되며, 이는 이전과 같이 t1 스레드에서 시작된다. 흥미로운 것은 다른 스레드 stop_req_thread가 실행되어 람다 캡처에서 t1을 캡처하고 이 스레드 안에서 t1의 request_stop이 실행된다는 것이다.
이로 인해 stop_callback은 thread stop_req_thread에서 실행되며, 출력에서 관찰할 수 있는 것과 같이 thread t1에 생성되었음에도 불구하고 실행된다.
```js
stop foo in thread: 139771773687616
thread id of foo: 139853637019392
from foo: 1 seconds elapsed
from foo: 2 seconds elapsed
from stop_foo: 0 seconds elapsed
from foo: 3 seconds elapsed
from stop_foo: 1 seconds elapsed
stop_req_thread id: 139771765290752
foo requested to stop
stop callback executed from 139771765290752
```
<div class="content-ad"></div>
전체 예는 컴파일러 탐색기에서 이 링크에 액세스할 수 있습니다.
# 3. 요약
공유 중지 상태를 사용하는 방법에는 두 가지가 있다: (1) jthread의 공유 중지 상태를 사용하는 방법 또는 (2) 공유 중지 상태를 명시적으로 생성하고 jthread의 중지 토큰에 묶는 방법. 이 글에서 우리는 첫 번째 방법을 살펴보았다.
이 방법에서, 우리는 jthread::request_stop에 대한 명시적인 호출에 의해 또는 jthread의 소멸자가 실행될 때 jthread의 공유 정지 상태와 관련된 stop_token이 정지 신호 viz를 수신하는 메커니즘을 살펴보았다.
각각의 경우에 실행 중인 태스크는 jthread의 생성자가 태스크와 태스크의 다른 인수를 수신할 때 jthread의 내부 공유 정지 상태에 묶이는 첫 번째 선택적 파라미터로서 stop_token을 가져야 한다.아라미터).
<div class="content-ad"></div>
마지막으로 stop_callback은 레이스 조건을 방지하는 RAII 측면에서 매우 다재다능한 jthread 의 공유 정지 상태로 등록될 수 있다. stop_callback이 포함된 이 기사에서 간단한 예만 살펴보았지만, C++20의 공동성 기능 4번째 호에서는 stop_callback의 사용법과 jthread를 공동으로 중지하는 두 번째 방법에 대해 자세히 살펴보겠습니다.
# 4. 참고 문헌
[1] 중단에 대한 설명: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0660r10.pdf의 4페이지
[2] 스레드를 죽이면 실행 중인 스레드가 작업 중간에 중단되어 리소스가 누출될 수 있습니다. 또한 스레드를 죽이면 일반적으로 OS 커널을 처리해야 합니다.
[3] std::stop_callback의 cpp 참조 페이지 : https://en.cppreference.com/w/cpp/thread/stop_callback
<div class="content-ad"></div>
'프로그래밍' 카테고리의 다른 글
Angular의 모든 요청 오류 탐지 (0) | 2022.01.13 |
---|---|
일반론자의 주장: 유형별 물리 및 프로그래밍에 대해 (0) | 2022.01.13 |
당신의 비전을 실현하는 방법(나의 실수) (0) | 2022.01.12 |
C++ 개발자를 위한 가장 흥미로운 블로그 및 웹 사이트 (0) | 2022.01.12 |
JavaScript 오브젝트 속성의 하위 집합을 가져오는 방법 (0) | 2022.01.12 |
댓글