powered by simpleCommunicator - 2.0.55     © 2025 Programmizd 02
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Форумы / WinForms, .Net Framework [игнор отключен] [закрыт для гостей] / Синхронизация потоков через именованные мьютексы
15 сообщений из 15, страница 1 из 1
Синхронизация потоков через именованные мьютексы
    #38688957
Фотография Compositum
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Доброго времени суток.

В .NET синхронизировать процессы можно при помощи именованных мьютексов и семафоров. Экспериментирую по обозначенной теме.

Например, пусть два процесса выполняют запись в один и тот же текстовый файл: первый процесс пишет некоторое количество раз слово "dog", а второй - слово "cat". При этом нужно организовать работу процессов так, чтобы слова чередовались. Т.е. на выходе должен получиться файл со следующим содержимым:
Ожидаемый текстовый файл dog
cat
dog
cat
dog
cat
dog
cat
...
dog
cat
Моё решение выглядит следующим образом:
Первое приложение (Launcher.exe) создаёт именованный мьютекс и блокирует его. Затем создаются два идентичных процесса (ConsoleApplication2.exe), синхронизация которых должна происходить через обозначенный мьютекс. Когда процессы созданы, с мьютекса снимается блокировка и процессы запускаются...

Однако по факту начало текстового файла начинается не так, как мне бы того хотелось. Например, это может выглядеть так:
Фактический текстовый файл dog
dog
dog
dog
cat
dog
cat
dog
cat
dog
cat
dog
cat
dog
cat
...
Т.е. согласно результату, в моём коде синхронизация происходит не сразу, а спустя некоторое время. Хотелось бы понять, где я напортачил в коде.

Код Launcher.exe:
Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
// Program.cs
// It builds the Launcher.exe
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Launcher {

  class Program {
    static void Main(string[] args) {

      Console.Title = "Launcher";
      String fileFullName = @"c:\_threads_laboratory\data.txt";

      using (Mutex mutex = new Mutex(true, "my_mutex")) {

        if (File.Exists(fileFullName))
          File.Delete(fileFullName);

        Process proc_1 = new Process();
        String exeName = @".\ConsoleApplication2.exe";
        ProcessStartInfo info_1 = new ProcessStartInfo(exeName, "proc_#1 dog");
        proc_1.StartInfo = info_1;
        proc_1.Start();
        Console.WriteLine("proc_#1 started by launcher...");

        Process proc_2 = new Process();
        ProcessStartInfo info_2 = new ProcessStartInfo(exeName, "proc_#2 cat");
        proc_2.StartInfo = info_2;
        proc_2.Start();
        Console.WriteLine("proc #2 started by launcher...");

        mutex.ReleaseMutex();

        proc_1.WaitForExit();
        proc_2.WaitForExit();
      }

      Console.WriteLine("Result in the \"{0}\" file.", fileFullName);
      Console.WriteLine("Press any key for exit...");
      Console.ReadKey();
    }
  }
}



Код ConsoleApplication2.exe:
Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
// Program_2.cs
// It builds the ConsoleApplication2.exe
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;

namespace ConsoleApplication2 {

  class Program_2 {

    static void Main(string[] args) {
      if (2 != args.Length)
        return;

      Console.Title = args[0];

      Mutex mutex = new Mutex(false, "my_mutex");

      String dir = @"c:\_threads_laboratory";
      String file = "data.txt";
      String fullName = Path.Combine(dir, file);
      if (!Directory.Exists(dir))
        Directory.CreateDirectory(dir);
      String text = args[1];
      Int32 counter = 100;

      using (FileStream fs = File.Open(fullName, FileMode.Append,
        FileAccess.Write, FileShare.ReadWrite)) {
        using (StreamWriter sw = new StreamWriter(fs)) {
          while (counter-- > 0) {
            mutex.WaitOne();
            fs.Position = fs.Length;
            sw.WriteLine(text);
            Console.Write("*");
            sw.Flush();
            fs.Flush(true);
            mutex.ReleaseMutex();
            Thread.Sleep(0);
          }
          sw.Close();
        }
        fs.Close();
      }
    }
  }
}


Буду признателен за конструктивные замечания по теме.

Спасибо.
...
Рейтинг: 0 / 0
Синхронизация потоков через именованные мьютексы
    #38689023
cdtyjv
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Compositum ,
Давайте отталкиваться от теории. В мире синхронизации есть классическая структура - монитор Хоара, погуглите. Он состоит из двух фич:
1) Mutual exclusion - когда один поток занял его, другой не может его занять.
2) Conditional variables - когда один поток занял монитор, но внутри критической секции понял, что какое-то условие не выполнено, он может временно отпустить монитор, что бы другой поток его занял, и, возможно, перевел систему в такое состояние, что бы условие первого потока оказалось выполненным, и он смог продолжить работу. Когда второй поток это делает, то перед тем, как отпустить монитор, он сигнализирует другим потокам, что те могут попробовать заново проверить те условия, на которых они обломались.
Это самые основы.

В чем изъян вашего решения? В том, что вы используете только mutual exclusion, а вам нужна еще и conditional variable. То есть, ваш алгоритм должен выглядеть так:
1) Поток 1 занимает мьютекс.
2) Поток 1 проверяет, что было записано в файл последний раз.
2.1) Если ничего, или же последним писал поток 2 - то записать свою строку.
2.2) Если же последний раз писал этот же поток, то отпустить мьютекс, и ждать, пока второй поток что-то не запишет.
Только в такой реализации у вас реально будут чередоваться строки.

В вашей же реализации этого условия нет. Поэтому первый поток (процесс) стартует первым, и начинает записывать свою строку много раз. Потом через некоторое время стартует второй поток, и теперь строки начинают чередоваться. Но чередуются они только потому, что захват мьютекса ОС достаточно дорогая операция, а потому как только первый поток отпускает его, второй поток почти всегда успевает захватить его первым. Если вы проделаете тот же трюк, например, с объектом Monitor, или с локальным мьютексом в рамках одного процесса, то вы увидите, что ваше решение не работает не только в начале, но не работает вовсе.

Отправная точка вам дана, дальше разбирайтесь сами. Если будет снова непонятно - спрашивайте.
...
Рейтинг: 0 / 0
Синхронизация потоков через именованные мьютексы
    #38689090
Фотография Compositum
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
cdtyjv2) Поток 1 проверяет, что было записано в файл последний раз.
2.1) Если ничего, или же последним писал поток 2 - то записать свою строку.
2.2) Если же последний раз писал этот же поток, то отпустить мьютекс, и ждать, пока второй поток что-то не запишет.
Я думал об этом варианте решения в процессе написания кода, но полагал, что мьютекса в данном случае будет достаточно. Мой текущий выбор был обусловлен следующими соображениями:

1. Launcher.exe запускает оба процесса, и они оба моментально "замирают в ожидании", дойдя до строчки кода
Код: c#
1.
mutex.WaitOne();


2. После того, как Launcher.exe освобождает мьютекс
Код: c#
1.
mutex.ReleaseMutex();


Его тут же захватывает любой из ожидающих процессов и выполняет один виток в цикле. В коде этого цикла, сразу после освобождения мьютекса, присутствует строка
Код: c#
1.
Thread.Sleep(0);


Её задача - не дать потоку данного процесса сразу пойти на очередной виток (это возможно, т.к. мьютекс освобождён), но вместо этого выполнить переключение исполняемых потоков (в данном случае на поток, выполняемый в др. процессе), при условии что их несколько.

Мои предположения о причине проблемы в текущем коде были следующими:

1. Возможно один из процессов не успевает дойти до точки
Код: c#
1.
mutex.WaitOne();


в то время как в коде Launcher.exe мьютекс уже освобождён. Для решения этого я пробовал в Launcher.exe, перед освобождением мьютекса ставить такой код:
Код: c#
1.
Thread.Sleep(100);


чтобы оба процесса успели дойти до "нужной кондиции". Однако это не помогло.

2. Возможно метод
Код: c#
1.
Thread.Sleep(0);

ориентирован на переключение потоков в рамках одного процесса.

P.S. Спасибо за ответ, добавлю в код conditional variable.
...
Рейтинг: 0 / 0
Синхронизация потоков через именованные мьютексы
    #38689092
Фотография Compositum
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Сейчас в голову пришёл третий вариант:

3. Возможно переключение происходит не на второй процесс, как я ожидал, а на Launcher.exe, который замер в ожидании завершения работы процессов. В виду этого тут же происходит очередное переключение, но не на второй процесс, а всё на тот же, первый (т.е. обратно к тому, кто "пнул мячик").
...
Рейтинг: 0 / 0
Синхронизация потоков через именованные мьютексы
    #38689180
Фотография D129
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
CompositumСейчас в голову пришёл третий вариант:

3. Возможно переключение происходит не на второй процесс, как я ожидал, а на Launcher.exe, который замер в ожидании завершения работы процессов. В виду этого тут же происходит очередное переключение, но не на второй процесс, а всё на тот же, первый (т.е. обратно к тому, кто "пнул мячик").

Переключением процессов управляет виндовс, и произвольным образом.
Вы на это повлиять не можете.
Кроме того, поведение будет другим, если вы запустите ваши аппликации на многопроцессорной системе.
...
Рейтинг: 0 / 0
Синхронизация потоков через именованные мьютексы
    #38689193
Arm79
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Compositumпусть два процесса выполняют запись в один и тот же текстовый файл
Это теоретическая или практическая задача? Если практическая, то это не самый удачный вариант. Запись в файл - медленный процесс, и было бы правильнее сделать так, чтобы в файл писал только один поток. Конечно, это не абсолютное утверждение, и в популярных библиотеках логирования есть поддержка записи кучи потоков/процессов в один файл, но это непопулярное решение.

Compositumпервый процесс пишет некоторое количество раз слово "dog", а второй - слово "cat". При этом нужно организовать работу процессов так, чтобы слова чередовались
Далее, сама по себе идея обеспечения очередности следования потоков противоречит самой идее параллельного программирования

CompositumПервое приложение (Launcher.exe) создаёт именованный мьютекс и блокирует его
Зачем вообще в первом приложении создавать мьютекс? Что, вторые приложения между собой сами не могут разрулить ситуацию?
...
Рейтинг: 0 / 0
Синхронизация потоков через именованные мьютексы
    #38689238
Фотография Compositum
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Arm79Это теоретическая или практическая задача?
Это просто "хелло ворлд" для себя, чтобы пощупать на примере.
Arm79Зачем вообще в первом приложении создавать мьютекс? Что, вторые приложения между собой сами не могут разрулить ситуацию?
Путём введения третьего приложения я планировал подвести оба процесса к "стартовой черте на беговой дорожке", чтобы они находились в ожидании в одной и той же точке кода.
...
Рейтинг: 0 / 0
Синхронизация потоков через именованные мьютексы
    #38689247
Arm79
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Ну если только для примера, вот код с упрощениями (не два процесса, а два потока, не файл, а консоль)

Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
using System;
using System.Threading;

namespace ConsoleApplication4
{
    class Program
    {
        private static readonly Thread _thread1 = new Thread(ThreadHandler) {IsBackground = true};
        private static readonly Thread _thread2 = new Thread(ThreadHandler) {IsBackground = true};

        private static void ThreadHandler(object state)
        {
            Mutex mtx = Mutex.OpenExisting("SQL.RU");

            while (true)
            {
                mtx.WaitOne();
                try
                {
                    var rnd = new Random();
                    int count = rnd.Next(1, 11);

                    for (int i = 0; i < count; i++)
                    {
                        Console.WriteLine((string)state);
                        Thread.Sleep(100);
                    }
                }
                finally
                {
                    mtx.ReleaseMutex();
                }

                Thread.Sleep(1000);
            }
        }

        static void Main(string[] args)
        {
            var mtx = new Mutex(false, "SQL.RU");
            try
            {
                _thread1.Start("dog");
                _thread2.Start("cat");

                Console.ReadLine();
            }
            finally
            {
                mtx.Dispose();
            }
        }
    }
}
...
Рейтинг: 0 / 0
Синхронизация потоков через именованные мьютексы
    #38689251
Arm79
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
CompositumПри этом нужно организовать работу процессов так, чтобы слова чередовались
Блин, этого не увидел... Сейчас...
...
Рейтинг: 0 / 0
Синхронизация потоков через именованные мьютексы
    #38689253
Фотография Konst_One
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
CompositumПутём введения третьего приложения я планировал подвести оба процесса к "стартовой черте на беговой дорожке", чтобы они находились в ожидании в одной и той же точке кода.


MSMQ может тогда уж лучше юзать?
...
Рейтинг: 0 / 0
Синхронизация потоков через именованные мьютексы
    #38689256
Фотография Compositum
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Konst_OneMSMQ может тогда уж лучше юзать?
На данный момент я "щупаю" мьютексы. :)
...
Рейтинг: 0 / 0
Синхронизация потоков через именованные мьютексы
    #38689258
Фотография Compositum
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Arm79Блин, этого не увидел...
А это как раз основное :)
...
Рейтинг: 0 / 0
Синхронизация потоков через именованные мьютексы
    #38689527
Arm79
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
CompositumПри этом нужно организовать работу процессов так, чтобы слова чередовались

Честно говоря, с мьютексами не получилось. Кажется, такой тип работы через мьютексы реализовать затруднительно. Зато межпроцессное взаимодействие можно организовывать и на основе именованных событий.

Код: c#
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
using System;
using System.Runtime.InteropServices;
using System.Threading;
using Microsoft.Win32.SafeHandles;

namespace ConsoleApplication4
{
    class Program
    {
        [DllImport( "KERNEL32.DLL", EntryPoint="CreateEventW", SetLastError=true )]
        private static extern SafeWaitHandle CreateEvent(IntPtr lpEventAttributes, bool bManualReset, bool bInitialState, string lpName );

        [DllImport("KERNEL32.DLL", EntryPoint = "OpenEventW", SetLastError = true)]
        private static extern SafeWaitHandle OpenEvent(uint dwDesiredAccess, bool bInheritHandle, string lpName);

        public static ManualResetEvent CreateManualResetEvent(bool initialState, string name)
        {
            SafeWaitHandle rawEvent = CreateEvent(IntPtr.Zero, true, initialState, name);
            return new ManualResetEvent(initialState) {SafeWaitHandle = rawEvent};
        }

        public static ManualResetEvent OpenManualResetEvent(string name)
        {
            SafeWaitHandle rawEvent = OpenEvent(0x001F0003 | 0x00000002, true, name);
            return new ManualResetEvent(false) { SafeWaitHandle = rawEvent };
        }


        private static readonly Thread _thread1 = new Thread(ThreadHandler1) {IsBackground = true};
        private static readonly Thread _thread2 = new Thread(ThreadHandler2) {IsBackground = true};

        private static void ThreadHandler2(object state)
        {
            var mtx1 = OpenManualResetEvent("1_SQL.RU");
            var mtx2 = OpenManualResetEvent("2_SQL.RU");
            string txt = (string)state;

            while (true)
            {
                mtx2.WaitOne();
                mtx2.Reset();

                Console.WriteLine(txt);
                Thread.Sleep(10);

                mtx1.Set();
            }
        }

        private static void ThreadHandler1(object state)
        {
            var mtx1 = OpenManualResetEvent("1_SQL.RU");
            var mtx2 = OpenManualResetEvent("2_SQL.RU");
            string txt = (string)state;

            while (true)
            {
                mtx1.WaitOne();
                mtx1.Reset();

                Console.WriteLine(txt);
                Thread.Sleep(10);

                mtx2.Set();
            }
        }

        static void Main(string[] args)
        {
            var mtx1 = CreateManualResetEvent(true, "1_SQL.RU");
            var mtx2 = CreateManualResetEvent(false, "2_SQL.RU");
            
            try
            {
                _thread1.Start("dog");
                _thread2.Start("cat");

                Console.ReadLine();
            }
            finally
            {
                mtx1.Dispose();
                mtx2.Dispose();
            }
        }
    }
}
...
Рейтинг: 0 / 0
Синхронизация потоков через именованные мьютексы
    #38689854
Фотография Compositum
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Arm79Зато межпроцессное взаимодействие можно организовывать и на основе именованных событий.
Спасибо за вариант, правда этот код демонстрирует взаимодействие между потоками в рамках одного и того же процесса, а не межпроцессорное взаимодействие.
...
Рейтинг: 0 / 0
Синхронизация потоков через именованные мьютексы
    #38689860
Arm79
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Compositumправда этот код демонстрирует взаимодействие между потоками в рамках одного и того же процесса, а не межпроцессорное взаимодействие.
Если бы речь шла о межпотоковом, я бы не заморачивался с p/invoke и вызвал бы конструктор событий нативно. Именованные события как раз для межпроцессорного.

К тому же обратите внимание, что в потоках идет получение Event посредством вызова соответствующего API. Если бы речь шла только о потоках, передал бы по ссылке или через поля класса.
...
Рейтинг: 0 / 0
15 сообщений из 15, страница 1 из 1
Форумы / WinForms, .Net Framework [игнор отключен] [закрыт для гостей] / Синхронизация потоков через именованные мьютексы
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]