13,032
回編集
 (→排他処理)  | 
				|||
| 376行目: | 376行目: | ||
**: 読み取りと書き込みの頻度のバランス  | **: 読み取りと書き込みの頻度のバランス  | ||
<br>  | <br>  | ||
====   | ==== 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);  // 書き込み操作の間隔  | |||
        }  |         }  | ||
     }  |      }  | ||