Skip to content

Common Idioms / Design Patterns

References

Background Thread Periodic Activity

Home-grown, but commonly used

class MyService {
 public:
  Status Start();
  void Stop();

 private:
  void BackgroundThreadEntryPoint();

  std::unique_ptr<std::thread> background_thread_;
  absl::Notification stopping_notification_;
};

Status MyService::Start() {
  background_thread_ = std::make_unique<std::thread>(
      &MyService::BackgroundThreadEntryPoint, this);
  return Status::OK();
}

void MyService::Stop() {
  stopping_notification_.Notify();
  if (background_thread_) {
    background_thread_->join();
    background_thread_.reset();
  }
}

void MyService::BackgroundThreadEntryPoint() {
  while (!stopping_notification_.WaitWithTimeout(
      absl::Milliseconds(kLoopInterval))) {
    // ...
  }
}

Thread-safe Lazy-initialized Singleton

Note

Unless we truly need a global object (for example GlobalBackgroundThreadPool), we should avoid the Singleton pattern. Prefer passing required objects through constructors or setter functions to achieve better composability and testability.

Factory& Factory::GetInstance() {
  static base::NoDestructor<Factory> instance;
  return *instance;
}

Assume for now we ignore what base::NoDestructor actually is and pretend it looks like this:

Factory& Factory::GetInstance() {
  static Factory instance;
  return instance;
}
  1. This only initializes the static Factory instance the first time Factory::GetInstance() is called.
  2. Concurrent multi-threaded calls to Factory::GetInstance() are safe; the static instance is initialized exactly once.

TODO

  1. Explain why (thread-safe initialization semantics of function-local statics since C++11)
  2. Explain what NoDestructor does

Implementing Copy-On-Write using std::shared_ptr

From the book Linux Multithreaded Server Programming: Using the muduo C++ Network Library.

class MyQueue {
 public:
  // Omitted: constructor, destructor, other methods.

  void Push(int v);
  void Print();

 private:
  std::shared_ptr<std::vector<int>> queue_ ABSL_GUARDED_BY(mutex_);
  absl::Mutex mutex_;
};

void MyQueue::Push(int v) {
  absl::MutexLock lock(&mutex_);
  if (!queue_.unique()) {
    // Copy if other people reading the queue.
    queue_ = std::make_shared<std::vector<int>>(*queue_);
  }
  // Now we ensure no other people accessing the queue.
  DCHECK(queue_.unique());
  queue_->emplace_back(v);
}

void MyQueue::Print() {
  std::shared_ptr<std::vector<int>> the_queue;
  {
    absl::MutexLock lock(&mutex_);
    the_queue = queue_;
  }

  for (int v : *the_queue) {
    absl::PrintF("%d\n", v);
  }
}

Comments