C 언어의 고급 루틴++
파이톤은 통상적으로'배터리 함유'라고 불리는데, C++의 핵심 원칙 중 하나는'당신이 사용하지 않는 물건을 위해 비용을 지불하지 마라'는 것이다.C++의 표준 템플릿 라이브러리 (STL) 에서도 이 점을 볼 수 있으며, 일부러 작은 범위 내에서 유지된다.그 중 하나는 STL에서 제공하는 스레드 기능이다.Python의 표준 라이브러리는 저급 스레드 동기화 원어 (스레드, 잠금) 부터 고급 스레드 동기화 원어 (작업, 스레드 탱크, 미래) 까지 모든 내용을 포함합니다.C++는 더 높은 레벨만 제공합니다.전화번호
std::future
.불행하게도 이러한 미래를 되돌려주는 std::async
함수는 통상적으로 std::thread
의 포장기로만 이루어진다.이렇게 하는 것은 매번 호출std::async
할 때마다 라인 생성과 폐기 비용이 발생한다는 단점이 있다.이것이 바로 내가 통상적으로 스레드 탱크나 생산자-소비자 모델을 더 좋아하는 이유다.이 두 가지 모델은 작업이나 작업 단원을 포함하는 대기열이 필요합니다.작업 루틴은 이 대기열에서 항목을 읽고 처리하려고 계속 시도할 것입니다.일부 라이브러리는 스레드 안전 대기열과/또는 스레드 탱크, 예를 들어 poco, QT 또는 boost를 제공하지만 실제로는 STL만 사용하여 자신의 스레드 안전 대기열을 실현하는 것이 매우 간단하다.이제 우리 하나 이루자!
기본 레이아웃부터 시작하여
SynchronisationQueue
이라는 클래스를 만들고 있습니다. 이 클래스는 두 가지 방법 put
과 get
을 포함하고 있으며, 파이톤 기반 SimpleQueue 클래스를 느슨하게 만들고 있습니다.template<typename T>
class SynchronisationQueue {
public:
void put(const &T val);
T get();
private:
std::mutex mtx_;
std::queue<T> queue_;
};
put
의 실현은 매우 간단하다.우리는 대기열에 대한 접근을 보호하기 위해 잠금 보호 장치를 사용하고 항목을 대기열로 간단하게 밀어붙여야 한다.get
좀 무겁습니다.대기열에 항목이 포함되어 있는지 확인해야 합니다. 만약 포함되어 있다면, 우리는 이 항목을 검색하고 계속할 수 있지만, 만약 포함되지 않는다면, 다른 항목이 대기열에 들어갈 때까지 기다려야 합니다.간단하지만 유치한 실현은 다음과 같다.void put(const &T val)
{
std::lock_guard<std::mutex> lck(mtx_);
queue_.push(val);
}
T get()
{
while (true)
{
{
std::lock_guard<std::mutex> lck(mtx_);
if (queue_.size() > 0)
{
T ret_val = std::move(queue_.front());
queue_.pop();
return ret_val;
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
이런 실현에는 뚜렷한 결점이 있다.수면 X밀리초는 cpu주기를 낭비할 수 있으며, 시간 초과를 증가시켜 cpu주기를 최소화할 수 있지만, 이것은 반대로 대기열의 흡수량에 부정적인 영향을 미칠 수 있다.여기서
std::condition_variable
는 구제 역할을 한다. 이것은 동기화 원어로 다른 라인이 대기 조건을 수정하고 대기 라인을 알릴 때까지 막을 수 있다.std::condition_variable
두 가지 유형의 구성원 기능이 있으며, 각 기능은 필요에 따라 다릅니다.wait: 이 함수는 기다리는 루틴에서 호출됩니다. JobQueue의 경우 새 프로젝트를 처리하는workerthread를 기다립니다.
notify: 이 함수는 알림 라인에서 호출되었습니다. 이 라인은 조건이 바뀌었고, 기다리는 라인이 깨어나기를 희망합니다.
cv
가 있다면 가장 기본적인 용법은 다음과 같다.// thread 1 waits for the condition
while(condition)
cv.wait(lck);
// thread 2 notifies that thread 1 can continue
condition = true;
cv.notify_one()
cv.wait()
자물쇠를 풀고 다른 라인에서 cv.notify_one()
또는 cv.notify_all()
까지 휴면한다.그리고 condition_variable
자물쇠를 잠그고 다시 가져옵니다1. 그러면 보호된 자원을 안전하게 계속 사용할 수 있습니다.지금 당신은 우리가 왜while 순환 속에서 조건을 끊임없이 검사해야 하는지 알고 싶을 것이다.여러 개의 라인이 관련되었을 때 라인 2 설정
condition=true
사이에 세 번째 라인이 없으면 그것을 다시 설정할 수 없다false
.그러나 두 개의 라인만 있어도 조건 변수는 알림을 받지 못한 상태에서 깨어날 수 있다.이는 허위 각성이라고 불리며 C++ 기준은 std::condition_variable
의 실현에 약간의 유연성을 제공할 수 있지만 얼마나 많은 실현이 이런 자유2를 실제로 사용했는지는 아직 알 수 없다.따라서, 조건 변수를 알린 후, 항상 조건을 검사해야 합니다.while 순환에서 가장 쉽게 완성할 수 있습니다.한 마디로 하면 조건 변수를 사용하려면 다음과 같은 세 가지 부분이 필요하다.
SynchronizationQueue
의 경우 우리가 사용할 조건은 대기열이 비어 있는 것입니다.그것이 비어 있을 때, 우리는 다른 라인이 원소를 대기열에 추가하기를 기다릴 것입니다.이 스레드는 대기하는 스레드를 알려야 합니다. 계속 대기열에서 항목을 검색할 수 있음을 표시합니다.중요한 것은 우리가 자물쇠를 가지고 있지 않은 상황에서 대기열의 크기를 바꿀 수 없다는 것이다. 왜냐하면 이것은 경쟁 조건을 초래할 수 있기 때문이다.여러 라인에서 접근
std::queue
하는 뚜렷한 문제 외에 조건 변수에도 문제가 있을 수 있다.자물쇠가 없는 상태에서 항목을 대기열에 삽입할 수 있도록 허용하면, 검사 조건 (대기열이 비어 있음) 의 작업 라인과 시작 대기 조건 변수 사이에 항목을 삽입할 수 있습니다.이것은 작업 라인이 대기 상태에 있을 수도 있고, 실제로는 대기열의 항목을 처리할 수도 있습니다.이것이 바로 조건이 원자적으로 바뀔 수 있어도 항상 잠겨 있어야 하는 이유다.다음 예에서 조건 변수
empty_queue_cv_
를 SynchronizationQueue의 개인 구성원 데이터에 추가합니다.우리는 조건 변수를 사용하기 위해 get
방법을 다시 쓸 수 있다.T get()
{
std::unique_lock<std::mutex> lck(mtx_);
while (queue_.empty())
empty_queue_cv_.wait(lck);
T ret_val = std::move(queue_.front());
queue_.pop();
return ret_val;
}
마지막 단계는 put
방법에 알림을 추가하여 기다리는 라인을 깨우는 것입니다.put
대기열에 항목 하나만 추가하면 충분하기 때문이다.만약 우리가 여러 요소를 삽입하는 방법을 추가하려고 한다면, 예를 들어 notify_one()
범위 삽입을 추가하려면, 우리는 std::vector
를 사용하여 모든 대기 라인을 깨울 수 있다.void put(const &T val)
{
std::lock_guard<std::mutex> lck(mtx_);
queue_.push(val);
empty_queue_cv_.notify_one()
}
완전한 notify_all()
구현은 here에서 찾을 수 있습니다.나는 시간 초과와 시간 초과가 없는 SynchronizationQueue
등 몇 가지 실용적인 기능을 포함했다.보시다시피 이러한 기본 구축 블록을 사용하면 다른 언어에 내장된 고급 동기화 원어, 예를 들어 스레드 탱크, 신호량, 장벽과 알림을 실현할 수 있다.도전을 찾고 있다면, 여기에서 논의한 구축 블록을 사용하여 자신의 스레드 탱크를 실현할 수 있습니다. Github에도 몇 가지 실현이 있습니다. 보십시오.
즐거운 코딩!
이것도 우리가
get
를 lock_guard
로 업그레이드해야 하는 이유입니다. 이것은 유사하지만 후자는 당신이 밑바닥의 상호 배척을 풀고 다시 얻을 수 있도록 허락합니다. ↩ 별도: Spurious wakeup.거짓 깨우침도 조건 변수가 항상 조건과 함께 사용해야 하는 원인이다. 설령 이 조건이 단지 브리치일지라도. ↩
Reference
이 문제에 관하여(C 언어의 고급 루틴++), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/markboer/higher-level-threading-in-c-26lg텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)