4

私はスレッドセーフロギングの問題に対するさまざまなアプローチを見てきましたが、このようなものはまったく見たことがありません。および iostream。私が行った基本的なテストではうまくいくようです。

基本的に、標準マニピュレータ用に設定された operator<< を持つ Log クラス (クリエイティブ、私は知っています...) を持っているので、好きなものを喜んで渡すことができます。

ただし、次のようなことは承知しています。

std::cout << "Threads" << " will" << " mess" << " with" << "this." << std::endl;

複数のスレッドが cout (または Log ostream が指している場所) に書き込みを行っている場合、インターリーブされる可能性があります。そのため、これを可能にする Log クラスに固有のマニピュレータをいくつか作成しました。

Log::log << lock << "Write" << " what" << " I" << " want" << std::endl << unlock;

これが本質的にひどい考えであるかどうかを知りたいだけです.Logクラスのユーザーは「ロック」と「ロック解除」で訓練する必要があることを喜んで受け入れることを念頭に置いて. 「std::endl」を自動的にロック解除することを検討しましたが、それはより多くの頭痛の種を作成するように思われます... どうせテストで無秩序な使用法が出てくると思いますが、そのような使用法がコンパイルを引き起こす方法を誰かが見ることができれば-タイムエラー、いいですね。

また、コードをよりクリーンにするための提案もいただければ幸いです。

これは、デモンストレーション用のクラスの縮小バージョンです。全体には、ファイル名のようなものを取るコンストラクターがさらにいくつかあるため、質問にはあまり関係ありません。

#include <iostream>
#include <thread>
#include <fstream>

class Log{
public:
  //Constructors
  Log(std::ostream & os);
  // Destructor
  ~Log();
  // Input Functions
  Log & operator<<(const std::string & msg);
  Log & operator<<(const int & msg);
  Log & operator<<(std::ostream & (*man)(std::ostream &)); // Handles manipulators like endl.
  Log & operator<<(std::ios_base & (*man)(std::ios_base &)); // Handles manipulators like hex.
  Log & operator<<(Log & (*man)(Log &)); // Handles custom Log manipulators like lock and unlock.
  friend Log & lock(Log & log); // Locks the Log for threadsafe output.
  friend Log & unlock(Log & log); // Unlocks the Log once threadsafe output is complete.
private:
  std::fstream logFile;
  std::ostream & logStream;
  std::mutex guard;
};

// Log class manipulators.
Log & lock(Log & log); // Locks the Log for threadsafe output.
Log & unlock(Log & log); // Unlocks the Log once threadsafe output is complete.

void threadUnsafeTask(int * input, Log * log);
void threadSafeTask(int * input, Log * log);

int main(){
  int one(1), two(2);
  Log log(std::cout);
  std::thread first(threadUnsafeTask, &one, &log);
  std::thread second(threadUnsafeTask, &two, &log);
  first.join();
  second.join();
  std::thread third(threadSafeTask, &one, &log);
  std::thread fourth(threadSafeTask, &two, &log);
  third.join();
  fourth.join();
  return 0;
}

void threadUnsafeTask(int * input, Log * log){
  *log << "Executing" << " thread '" << *input << "', " << "expecting " << "interruptions " << "frequently." << std::endl;
}

void threadSafeTask(int * input, Log * log){
  *log << lock << "Executing" << " thread '" << *input << "', " << "not expecting " << "interruptions." << std::endl << unlock;
}

// Constructors (Most left out as irrelevant)
Log::Log(std::ostream & os): logFile(), logStream(logFile), guard(){
  logStream.rdbuf(os.rdbuf());
}

// Destructor
Log::~Log(){
  logFile.close();
}

// Output Operators
Log & Log::operator<<(const std::string & msg){
  logStream << msg;
  return *this;
}

Log & Log::operator<<(const int & msg){
  logStream << msg;
  return *this;
}

Log & Log::operator<<(std::ostream & (*man)(std::ostream &)){
  logStream << man;
  return *this;
}

Log & Log::operator<<(std::ios_base & (*man)(std::ios_base &)){
  logStream << man;
  return *this;
}

Log & Log::operator<<(Log & (*man)(Log &)){
  man(*this);
  return *this;
}

// Manipulator functions.
Log & lock(Log & log){
  log.guard.lock();
  return log;
}

Log & unlock(Log & log){
  log.guard.unlock();
  return log;
}

以下でコンパイルされたUbuntu 12.04 g ++で動作します。

g++ LogThreadTest.cpp -o log -std=c++0x -lpthread

カスタムマニピュレータの作成に関連するビットは、恥知らずにここから盗まれましたが、私の無能なコピーパスタのせいにしないでください.

4

3 に答える 3

3

ロックマニピュレータを明示的に渡す必要はありません。歩哨を使用できます(Hans Passantが言うように、RAIIセマンティクスを使用)

class Log{
public:
  Log(std::ostream & os);
  ~Log();

  class Sentry {
      Log &log_;
  public:
      Sentry(Log &l) log_(l) { log_.lock(); }
      ~Sentry() { log_.unlock(); }

      // Input Functions just forward to log_.logStream
      Sentry& operator<<(const std::string & msg);
      Sentry& operator<<(const int & msg);
      Sentry& operator<<(std::ostream & (*man)(std::ostream &)); // Handles manipulators like endl.
      Sentry& operator<<(std::ios_base & (*man)(std::ios_base &)); // Handles manipulators like hex.
    };

    template <typename T>
    Sentry operator<<(T t) { return Sentry(*this) << t; }
    void lock();
    void unlock();

private:
  std::fstream logFile;
  std::ostream & logStream;
  std::mutex guard;
};

今、書いている

Log::log << "Write" << " what" << " I" << " want" << foo() << std::endl;

意思:

  1. 一時的な Sentry オブジェクトを作成する
    • Log オブジェクトをロックする
  2. operator<<... 各呼び出しを親 Log インスタンスに転送します ...
  3. そして、式の最後 (またはfooスローの場合) で範囲外になります
    • Log オブジェクトのロックを解除します

これは安全ですが、多くの競合も引き起こします (メッセージのフォーマット中に、ミューテックスが通常よりも長くロックされます)。競合の少ないアプローチは、ロックをまったく使用せずにローカル ストレージ (スレッド ローカルまたはスコープ ローカル) にフォーマットし、それを共有ログ キューに移動するのに十分な時間だけロックを保持することです。

于 2013-10-22T14:16:46.620 に答える