C # simple sample code and usage of multi-process thread synchronization (Process Synchronization) when multi-process concurrent operations are implemented using Mutex,
This document describes sample code and test results for implementing multi-process thread synchronization (Process Synchronization) at the operating system level. The Code has been tested for reference and can be used directly.
Undertaking the business scenario of the previous blog [C # Use the read/write Lock three lines of code to solve the thread synchronization problem when multiple threads write files concurrently].
As the number of service processes increasesIn-process thread synchronization cannot meet current needs, Resulting inSimultaneous multi-processWhen writing data to the same file, the system prompts that the file is occupied.
In this scenario,Cross-process locksIs inevitable. In the reference provided by. NET, all process locks inheritSystem. Threading. WaitHandle class.
In this articleOnly single process (thread) operations are allowed at the same timeScenario,System. Threading. Mutex classIt is undoubtedly the simplest and most appropriate choice.
This type of objectThe name (string) mutex can be used to achieve the current session level.Or operating system levelSynchronization requirements. I chose synchronous writing examples at the operating system level because of wider coverage.
The following is the implementation code. I will not elaborate on the annotations in detail:
Namespace WaitHandleExample {class Program {static void Main (string [] args) {# region simple use // var mutexKey = MutexExample. getFilePathMutexKey ("file path"); // MutexExample. mutexExec (mutexKey, () => // {// Console. writeLine ("Code for Synchronous execution of processes"); //}); # endregion # region test code var filePath = Path. combine (AppDomain. currentDomain. baseDirectory, "test. log "). toUpper (); var mutexKey = MutexExample. getFilePathMutexKey (FilePath); // enable N write threads at the same time. for (0, LogCount, e => {// No mutex lock is used to write data, resulting in a large number of write errors. The FileStream constructor containing FileShare only implements intra-process thread synchronization, an error occurs when multiple processes write data at the same time. // WriteLog (filePath); // mutex lock is used to write data. Because there is only one thread operation at the same time, MutexExample does not occur. mutexExec (mutexKey, () =>{ WriteLog (filePath) ;}); Console. writeLine (string. format ("Log Count: {0 }. \ t \ tWrited Count: {1 }. \ tFailed Count: {2 }. ", LogCount. toString (), WritedCount. toString (), FailedCoun T. toString (); Console. read (); # endregion} // <summary> // C # sample code for mutex usage /// </summary> /// <remarks> has been tested and launched, you can directly use </remarks> public static class MutexExample {// <summary> // simple example of synchronous execution between processes /// </summary> // <param name = "action"> synchronous processing code </param> // <param name = "mutexKey"> synchronization key at the operating system level // (if the name is specified as null or empty string, create a local mutex. /// If the name is prefixed with "Global \", mutex is visible in all Terminal Server sessions. /// If the name is prefixed with "Local \", mutex is only visible in the terminal server session where it is created. /// If you do not specify a prefix when creating a named mutex, it uses the prefix "Local \".) </Param> /// <remarks> A simple example without retry and Exception Handling considerations </remarks> [Obsolete (error: false, message: "Use MutexExec")] public static void MutexExecEasy (string mutexKey, Action action) {// declare a named mutex to synchronize processes; this name is automatically created if the Mutex does not exist. if it already exists, you can directly obtain using (Mutex mut = new Mutex (false, mutexKey) {try {// lock, other threads must wait for the lock to be released before processing. If other threads are locked or preferentially locked, wait for the execution of other threads to complete. waitOne (); // execute the processing code (when WaitHandle is called. waitOne to WaitHandle. there is only one line in the ReleaseMutex period. Other threads have to wait until the lock is released before executing the code segment) action ();} finally {// release the lock so that other processes (or threads) can continue to execute the mut. releaseMutex ();}}} /// <summary> /// obtain the Process Synchronization key corresponding to the file name /// </summary> /// <param name = "filePath"> file path (case sensitive) and space) </param> // <returns> Process Synchronization key (mutex name) </returns> public static string GetFilePathMutexKey (string filePath) {// The synchronization key corresponding to the file generation, customizable format (mutex names support unfriendly special characters and are converted to BASE64 strings) var fileKey = Convert. toBase64String (Encoding. default. ge TBytes (string. format (@ "FILE \ {0}", filePath); // convert it to the synchronization key var mutexKey = string at the operating system level. format (@ "Global \ {0}", fileKey); return mutexKey ;} /// <summary> /// synchronous execution between processes /// </summary> /// <param name = "mutexKey"> operating system-level synchronization key ///( if you specify name as null or a null string, create a local mutex. /// If the name is prefixed with "Global \", mutex is visible in all Terminal Server sessions. /// If the name is prefixed with "Local \", mutex is only visible in the terminal server session where it is created. /// If you do not specify a prefix when creating a named mutex, it uses the prefix "Local \".) </Param> // <param name = "action"> synchronous processing operation </param> public static void MutexExec (string mutexKey, Action action) {MutexExec (mutexKey: mutexKey, action: action, recursive: false );} /// <summary> /// synchronous execution between processes /// </summary> /// <param name = "mutexKey"> operating system-level synchronization key ///( if you specify name as null or a null string, create a local mutex. /// If the name is prefixed with "Global \", mutex is visible in all Terminal Server sessions. /// If the name is prefixed with "Local \", mutex is only visible in the terminal server session where it is created. /// If you do not specify a prefix when creating a named mutex, it uses the prefix "Local \".) </Param> /// <param name = "action"> synchronous processing operation </param> /// <param name = "recursive"> indicates whether the current call is recursive., if an exception is detected during recursive processing, an exception is thrown to avoid infinite recursion </param> private static void MutexExec (string mutexKey, Action action, bool recursive) {// declare a named mutex to synchronize between processes. If the named mutex does not exist, it is automatically created. if it already exists, it is obtained directly // initiallyOwned: false: by default, the current thread does not have the ownership of an existing mutex, that is, the default thread is not the first thread that creates the named mutex. // Note: when a name mutex with the same name is declared concurrently, if the interval is too short, multiple mutex with the same name may be declared at the same time, and multiple mutex with the same name are not synchronized, for highly concurrent users, please handle using (Mutex mut = new Mutex (initiallyOwned: false, name: mutexKey) {try {// lock, other threads must wait for the lock to be released before processing. If other threads are locked or preferentially locked, wait for the execution of other threads to complete. waitOne (); // execute the processing code (when WaitHandle is called. waitOne to WaitHandle. in the ReleaseMutex period, only one thread can process the code segment. Other threads have to wait for the lock to be released.) action ();} // when other processes are locked and do not normally release the mutex lock (for example, the process is suddenly closed or exited), an AbandonedMutexException catch (AbandonedMutexException ex) will be thrown) {// avoid infinite recursion if (recursive) throw ex; // non-recursive call. When a mutex lock unlock exception is thrown by another process, retry MutexExec (mutexKey: mutexKey, action: action, recursive: true);} finally {// release the lock so that other processes (or threads) can continue to execute mut. releaseMutex () ;}}# region test file code static int LogCount = 500; static int WritedCount = 0; static int FailedCount = 0; static void WriteLog (string logFilePath) {try {var now = DateTime. now; var logContent = string. format ("Tid: {0} {1} {2 }. {3} \ r \ n ", Thread. currentThread. managedThreadId. toString (). padRight (4), now. toLongDateString (), now. toLongTimeString (), now. millisecond. toString (); File. appendAllText (logFilePath, logContent); WritedCount ++;} catch (Exception ex) {Console. writeLine (ex. message); FailedCount ++ ;}# endregion }}
If process synchronization is not used in the test, multiple threads are used to write files simultaneously:
Test results:The six processes perform 3000 write requests at the same time, and only 277 write requests are successfully performed.
The test uses mutex to synchronize processes. Multiple processes write files simultaneously with multiple threads:
Test results:Six processes simultaneously3000 write requests, all written successfully
Supplement:
The resource consumption and efficiency of process synchronization are much lower than that of thread synchronization. Please use it properly according to the actual scenario.
Although this article uses writing files as an example, the code usage scenarios for process synchronization are not related to file operations.
Although Semaphore class (signal lamp) can limit the number of threads simultaneously operated, and even set the maximum simultaneous operand to 1, the behavior is similar to that of Mutex class (Mutex; however, when a traffic signal exits abnormally in other processes, it cannot receive an exception notification. The exception can only be triggered by waiting for timeout, which is not suitable for the current scenario.
For more information about process synchronization and its application, see other materials.