ROBOT PAYMENT TECH-BLOG

株式会社ROBOT PAYMENTのテックブログです

並列処理と排他処理

こんにちは、ROBOT PAYMENTの開発統括室ペイメントシステム課のtaniguchikun です。 たまたま業務で並列処理と排他処理を実装することになったので、その辺について記事にしたいと思います。

並列処理とはなんぞや

恐らく本記事を読まれている方はエンジニアの方々だと思いますが、一応軽く解説をしたいと思います。 並列処理とは複数の処理を同時に行うといったものになります。

例え話

上記でピンとくる方は読み飛ばしていただいて問題ないです。 一般の方でプログラミングしたことがない方ですと例え話で説明した方がピンとくるものがあると思うので、現実世界のバスに例えたいと思います。 バスはバス停でお客様を乗り入れて複数の停車地点や終着地点まで運ぶといった事が主な業務になると思います。 しかし業務は同じでも停車地点や終着地点の違うバスも存在します。 すると片道1車線しかない道だと1本の道に複数のバスが一列に並んでしまい道が混雑します。 そこで片道1車線を2車線以上の道に工事すると、1車線にバスが停止したとしても残りの車線が空いているので道が混むことがなくなります。 ザックリな例えになりますがこの車線を複数にする事が並列処理になります。

排他処理

例え話の続きのように話していきたいと思います。 複数の車線がある中で1車線を工事することになった際に合流する箇所があると思います。 こちらもザックリな例えになりますが、合流する箇所が排他処理になります。

今回のキーワード

  1. 排他処理
  2. ミューテックス
  3. セマフォ
  4. Lock関数
  5. チェックポイント
  6. 静的変数

ミューテックス

例え話に出したバスで言うところの複数車線を合流箇所で1車線にする処理の事になります

※名前付きミューテックスはプロセスの領域を超えて共有する事が出来ます。  その例としてアプリケーションの2重起動の制御に使用されることが多いです。

セマフォ

ミューテックスでは合流箇所を1車線にしますが、合流箇所を5車線から3車線といったように車線を減らしますが、指定した車線数にする処理の事になります。

Lock関数(C#言語)

ミューテックスと同じ使われ方をします。 ※プロセスの領域を超えることは出来ません。

チェックポイント

静的変数

並列処理を行っているスコープ内で静的変数を使用すると他スレッドが参照や更新を行っていると、その影響で静的変数の値が変わってしまい想定の処理と変わってしまう可能性があります。 なので並列処理内で静的変数を扱う際には参照のみに限定するといった制限を行うと、一概には言えないですが不具合に繋がる可能性が低くなります。

サンプルコード

下記にC#でサンプルコードを用意しました。

ミューテックスの挙動確認

void Main()
{
    ThreadPool.GetMaxThreads(out int workerThreads, out int portThreads);
    Console.WriteLine("Worker threads={0}, Completion port threads={1}", workerThreads, portThreads);

    var time = DateTime.Now;
    Console.WriteLine($"開始");
    using (Mutex mutex = new Mutex(false))
    {
        var tasks = Enumerable.Range(0, 100).Select(i =>
        {
            var task = Task.Run(() =>
            {
                try
                {
                    mutex.WaitOne();
                    Thread.Sleep(2000);
                    Console.WriteLine($"スレッド ID:{i}");
                }
                finally
                {
                    mutex.ReleaseMutex();
                }
            });
            return task;
        }).ToArray();
        Task.WaitAll(tasks);
    }
    Console.WriteLine($"完了");
    var diffTime = DateTime.Now - time;
    Console.WriteLine(diffTime.TotalSeconds);
}

セマフォの挙動確認

void Main()
{
    ThreadPool.GetMaxThreads(out int workerThreads, out int portThreads);
    Console.WriteLine("Worker threads={0}, Completion port threads={1}", workerThreads, portThreads);
    
    Console.WriteLine($"開始");
    var time = DateTime.Now;
    using (Semaphore sem = new Semaphore(12, 12))
    {
        var tasks = Enumerable.Range(0, 100).Select(i =>
        {
            var task = Task.Run(() => {
                try
                {
                    sem.WaitOne();
                    Thread.Sleep(2000);
                    Console.WriteLine($"スレッド ID:{i}");    
                }
                finally {
                    sem.Release();
                }
            });     
            return task;
        }).ToArray();
        Task.WaitAll(tasks);
    }
    var diffTime = DateTime.Now - time;
    Console.WriteLine($"完了");
    Console.WriteLine(diffTime.TotalSeconds);
}

ロック関数の挙動確認

void Main()
{
    ThreadPool.GetMaxThreads(out int workerThreads, out int portThreads);
    Console.WriteLine("Worker threads={0}, Completion port threads={1}", workerThreads, portThreads);

    var time = DateTime.Now;
    Console.WriteLine($"開始");
    var lockObj = new object();

    var tasks = Enumerable.Range(0, 100).Select(i =>
    {
        var task = Task.Run(() =>
        {
            lock (lockObj)
            {
                Thread.Sleep(2000);
                Console.WriteLine($"スレッド ID:{i}");
            }
        });
        return task;
    }).ToArray();

    Task.WaitAll(tasks);

    Console.WriteLine($"完了");
    var diffTime = DateTime.Now - time;
    Console.WriteLine(diffTime.TotalSeconds);
}

最後まで読んでいただきありがとうございました。



We are hiring!!

ROBOT PAYMENTでは一緒に働く仲間を募集しています!!!

speakerdeck.com
www.robotpayment.co.jp