C++にて、非ラムダ式でミューテックスやスレッド処理をクラスに纏める方法

2020/01/17

C/C++ Linux アイデアノート 技術 考察

t f B! P L

スレッド処理とは

要は複数の処理を並列で処理して、
重い処理している間も別の処理は止まらないようにしましょう、
っていう処理です。

初歩的なプログラムは流れが一本ですが、
スレッド処理は複数の流れをCPUが縫うように処理を織り交ぜて、
少しずつ実行して行くので、
Thread (道筋 脈絡 織糸)と言われます。

スレッド処理ってなんで必要なんよ

なんにも考えないでスレッド処理を組むと、
同じメモリに対して複数から非同期的に内容読み書きしてデータを破損するとか、
デッドロックして処理が二度と終わらなくなるとか、
色々と問題が発生してきます。

ですが基本的に何らかの処理にすべてのパワーをつぎ込んで、
他の処理はなーんにもできませんでは、
コンピュータの使い方としてあまりに無駄が多いですし不便です。

なのでスレッディングはごくごく一般的に使用される処理ではありますし、
実際2011年の仕様からC++言語でも公式に提供されることになりました。

なるべくグローバルは汚したくないよね

前項で書いた、いろいろな問題を解決する方法の一つに
ミューテックス、というものがあります。

これはある処理があるコンピュータの機能を使用していたら、
つかってまーす!という旗を立てて、
その旗が立っている間は他の処理はじっと待つ、、、
といった同期処理に使用されるものです。

このmutex、スレッド化される処理が実装されている箇所、
全体で参照したい場合が多いので、
グローバルスコープに置かれている場合がよく見受けられます。

うん、許せん…

基本的に規模が大きくなればなるほど、
このmutexどこの処理たちがつかってんの!?
という問題が発生してくることになります。

そういう事態を防ぐために、
mutexをクラス内に収めてしまい、
それを使う処理もクラス内に定義することで、
使う処理を明確化します。

そのうえで、
処理を別々のスレッドで走らせながら単一のクラスインスタンスを見に行く、
という処理を実装する必要が出てくるわけです。

mutexのリファレンス(これ作ってくれた方スゲー)なんかを読むと、
ラムダ式を使って内容をクラスに纏めて書いたりする場合もあり、
実際それがわかりやすく書きやすい…と思うのですが、
開発チーム内でスキルが微妙に追いついていないメンバーにラムダ式を説明するのは、
何故か若干骨が折れます。

なので簡単に説明するためのサンプルコードとして、
以下のようなものをダダっと用意しておいてあります。


#include<iostream>
#include<thread>
#include<mutex>
#include<functional>

using namespace std;

class ClsThreadTest
{
private:
  mutex mtx;
  unsigned int testData;

public:
  ClsThreadTest() { testData ^= testData; }
  virtual ~ClsThreadTest();

  void* thread1();
  void* thread2();
};

ClsThreadTest::~ClsThreadTest()
{
}

void* ClsThreadTest::thread1()
{
  unique_lock ul(mtx, defer_lock);
  chrono::seconds d(1);

  for( int i ; i < 11 ; ++i )
  {
    ul.lock();
    testData++;
    ul.unlock();
    this_thread::sleep_for(d);
  }
}

void* ClsThreadTest::thread2()
{
  unique_lock ul(mtx, defer_lock);
  chrono::milliseconds d(500);

  for(;;)
  {
    ul.lock();
    cout << testData << endl;
    ul.unlock();

    this_thread::sleep_for(d);

    if(testData > 10)
    {
      break;
    }
  }
}

int main (int argc, char **argv )
{
  unsigned int Testdata;
  ClsThreadTest ctt;

  //bindしてクラス内の関数にクラスオブジェクトを紐付ける
  function thfunc1 = bind(mem_fn(&ClsThreadTest::thread1), &ctt);
  function thfunc2 = bind(mem_fn(&ClsThreadTest::thread2), &ctt);
  
  //通常の関数と同様にメンバー関数を呼ぶことができる。
  thread th1(thfunc1);
  thread th2(thfunc2);

  th1.join();
  th2.join();
  
  return 0;
}

実際にちゃんと書くとしたら、
thread th1([&ctt](ctt.thread1()));

とか1行で済んでしまうわけですが、
ラムダ式はそもそも関数型プログラミング言語と近い概念で、
手続き型のC/C++しかやってない人にはちょっと解りにくいようです。

結局これって何やってんの?

実際にソースコードのメイン処理でやっていることは、
上記1行のラムダ式と大して違いありません。

要は、
CTTというクラスインスタンスを、
bindでthread1関数を内包する無名関数の第一引数として束縛した状態で、
Callable型の関数ポインタに変換、
これをスレッドクラスのコンストラクタに与えて初期化しています。

つまりCTTへのインスタンスを、
ラムダ式で構築された無名関数内で展開して、
その関数ポインタをスレッドクラスのコンストラクタに与えて初期化している、
この一行の内容と(厳密にはちょっと違うけど)同じことをやっているわけです。

でもまぁ

私はラムダ式で書きますけどね。。。

Translate

ページビューの合計

注意書き

基本的にごった煮ブログですので、カテゴリから記事を参照していただけると読みやすいかと存じます。

ADBlocker等を使用していると、Twitterやアクセスカウンタが表示されません。

記事を読むには差し支えませんが、情報を参照したい場合には一時例外にしていただけると全てご参照いただけます。

Featured Post

ボイドラDICEの攻略法

QooQ