Chromium Chronicle #25: 스레드 안전 주석

에피소드 25: 샌프란시스코의 Victor Costan (2021년 10월)
이전 에피소드

C++에서 데이터 경합의 가능성을 배제하는 것은 모든 데이터 멤버 액세스에 대한 작은 스레드 안전 정확성 증명에 달려 있습니다. 특히 코드를 검토하거나 리팩터링할 때 이러한 증거로 인해 많은 정신적 부담이 가중됩니다. Clang의 정적 분석 프레임워크가 스레드 안전 증명의 반복 업무를 대체합니다.

스레드 안전하지 않은 클래스의 데이터 멤버에 GUARDED_BY_CONTEXT()를 추가합니다.

대부분의 Chrome 클래스는 스레드로부터 안전하지 않으며 단일 시퀀스에 사용해야 합니다. 스레드로부터 안전하지 않은 모든 데이터 멤버에 주석을 추가합니다. 불필요한 주석도 안전하지만 주석이 누락되면 데이터 경합이 발생할 위험이 있습니다.

#include "base/sequence_checker.h"  // for SEQUENCE_CHECKER()
#include "base/thread_annotations.h"  // for GUARDED_BY_CONTEXT()

class Cache {
  // Methods here.
 private:
  SEQUENCE_CHECKER(sequence_checker_);
  base::flat_map<std::string, std::string> data_ GUARDED_BY_CONTEXT(sequence_checker_);
};

Clang에 시퀀스 검사 적용

Clang은 데이터 멤버에 주석을 추가하는 대신 데이터에 액세스하는 모든 메서드가 이렇게 하기 전에 시퀀스 안전 검사를 실행하도록 합니다. 리팩터링 중에 코드가 이동함에 따라 Clang은 GUARDED_BY_CONTEXT() 주석을 계속 적용합니다.

void Cache::Set(base::StringPiece key, base::StringPiece value) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);  // Clang warns without this.
  data_.emplace(key, value);
}

뮤텍스를 사용하는 스레드 안전 클래스의 데이터 멤버에 GUARDED_BY() 추가

Chrome의 일부 클래스는 스레드 안전을 위해 잠금을 사용해야 합니다. 이 경우 스레드로부터 안전하지 않은 모든 데이터 멤버에 주석을 달아야 합니다. 각 주석은 데이터 멤버에 액세스하는 동안 보유해야 하는 뮤텍스를 가리킵니다.

#include "base/thread_annotations.h"  // for GUARDED_BY()

class ThreadSafeCache {
  // Methods here.
  private:
    base::Lock lock_;
    base::flat_map<std::string, std::string> data_ GUARDED_BY(lock_);
};

Clang에서 잠금 획득 적용

잠시 후 컴파일러에서 각 base::AutoLock의 범위가 올바르게 지정되고 잠금 Acquire()Release() 호출이 올바르게 페어링되도록 합니다.

void ThreadSafeCache::Set(base::StringPiece key, base::StringPiece value) {
  base::AutoLock auto_lock(lock_);  // Clang warns without this.
  data_.emplace(key, value);
}