「C Sharpの基礎 - マルチスレッド」の版間の差分

ナビゲーションに移動 検索に移動
376行目: 376行目:
**: 読み取りと書き込みの頻度のバランス
**: 読み取りと書き込みの頻度のバランス
<br>
<br>
==== lockキーワードの使用 ====
==== lockキーワード ====
lockキーワードのメリットと特徴を以下に示す。<br>
lockキーワードのメリットと特徴を以下に示す。<br>
* 使いやすさ
* 使いやすさ
490行目: 490行目:
       {
       {
           throw new IOException($"ファイルの書き込み中にエラーが発生: {ex.Message}", ex);
           throw new IOException($"ファイルの書き込み中にエラーが発生: {ex.Message}", ex);
      }
    }
}
</syntaxhighlight>
<br>
==== ReaderWriterLockSlimキーワード ====
以下の例では、複数のスレッドが頻繁に読み取りを行い、時々書き込みを行うシナリオを想定している。<br>
具体的には、共有されるデータ構造 (この場合は設定ファイル) に対して、多数の読み取り操作と少数の更新操作が行われる状況である。<br>
<br>
ReaderWriterLockSlimキーワードのメリットを以下に示す。<br>
* 複数の読み取り操作が同時に行われることを許可する。
* 書き込み操作が行われる場合には、他の全ての操作 (読み取りと書き込み) をブロックする。
<br>
ReaderWriterLockSlimキーワードが適している理由を以下に示す。<br>
* 書き込み時の整合性保護
*: 稀に発生する書き込み操作時には、全ての操作をブロックしてデータトレースを防ぐ。
* パフォーマンス
*: lockキーワードを使用した場合、全ての読み取り操作が直列化されてしまうが、
*: ReaderWriterLockSlimキーワードを使用することにより、読み取り操作のスループットが大幅に向上する。
<br>
読み取りが多く書き込みが少ない場合は、<code>ReaderWriterLockSlim</code>キーワードの使用が適切である。<br>
<br>
ただし、読み取りと書き込みの頻度が同程度の場合、あるいは、ロックの保持時間が非常に短い場合は、lockキーワードの方がオーバーヘッドが少なく、パフォーマンスが良い可能性がある。<br>
実務では、ベンチマークを取ることを推奨する。<br>
<br>
<syntaxhighlight lang="c#">
using System;
using System.IO;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
// ReaderWriterLockSlim を使用して、設定の読み取りと書き込みを同期するクラス
class ConfigManager
{
    private static readonly ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim();
    private static Dictionary<string, string> config = new Dictionary<string, string>();  // 設定を保持する (ファイルにも保存)
    private const string configFile = "config.txt";
    // 読み取りロックを使用
    public static string GetConfig(string key)
    {
      rwLock.EnterReadLock();
      try
      {
          return config.TryGetValue(key, out var value) ? value : null;
      }
      finally
      {
          rwLock.ExitReadLock();
      }
    }
    // 書き込みロックを使用
    public static void SetConfig(string key, string value)
    {
      rwLock.EnterWriteLock();
      try
      {
          config[key] = value;
          SaveConfig();
      }
      finally
      {
          rwLock.ExitWriteLock();
      }
    }
    private static void SaveConfig()
    {
      using (StreamWriter writer = new StreamWriter(configFile, false))
      {
          foreach (var kvp in config)
          {
            writer.WriteLine($"{kvp.Key}={kvp.Value}");
          }
      }
    }
    public static void LoadConfig()
    {
      rwLock.EnterWriteLock();
      try
      {
          config.Clear();
          if (File.Exists(configFile))
          {
            foreach (var line in File.ReadAllLines(configFile))
            {
                var parts = line.Split('=');
                if (parts.Length == 2)
                {
                  config[parts[0]] = parts[1];
                }
            }
          }
      }
      finally
      {
          rwLock.ExitWriteLock();
      }
    }
}
class Program
{
    static async Task Main()
    {
      ConfigManager.LoadConfig();
      var readTasks = new List<Task>();
      var writeTasks = new List<Task>();
      // 多数の読み取りタスクを作成
      for (int i = 0; i < 100; i++)
      {
          readTasks.Add(Task.Run(() => ReadConfig()));
      }
      // 少数の書き込みタスクを作成
      for (int i = 0; i < 5; i++)
      {
          writeTasks.Add(Task.Run(() => WriteConfig(i)));
      }
      await Task.WhenAll(readTasks.Concat(writeTasks));
      Console.WriteLine("全てのタスクが完了");
    }
    static void ReadConfig()
    {
      for (int i = 0; i < 1000; i++)
      {
          var value = ConfigManager.GetConfig("TestKey");
          Console.WriteLine($"Read: TestKey = {value}");
          Thread.Sleep(10);  // 読み取り操作の間隔
      }
    }
    static void WriteConfig(int writerIndex)
    {
      for (int i = 0; i < 10; i++)
      {
          ConfigManager.SetConfig("TestKey", $"Value{writerIndex}-{i}");
          Console.WriteLine($"Write: TestKey = Value{writerIndex}-{i}");
          Thread.Sleep(500);  // 書き込み操作の間隔
       }
       }
     }
     }

案内メニュー