Как лучше всего обрабатывать несколько задач, обращающихся к одному мьютексу? - PullRequest
1 голос
/ 09 апреля 2020

Итак, у меня есть несколько задач, которые блокируют / освобождают один и тот же мьютекс. После прочтения этого:

Пока блокировка взаимного исключения удерживается, код, выполняющийся в том же потоке выполнения, также может получить и снять блокировку. Однако выполнение кода в других потоках блокируется от получения блокировки до тех пор, пока блокировка не будет снята. 1

Я немного нервничал. Это должно означать, что две задачи могут обращаться к одному мьютексу, если они запланированы в одном потоке?

Для контекста у меня будет одна длительная задача (может быть заменена потоком) и несколько небольших задач, которые получить доступ к тому же мьютексу.

Чтобы уточнить, меня это беспокоит:

Тема 0:

 Run Task 1: Lock mutex, do some work

 Pause Task 1

 Run Task 2: Lock mutex, do some work, release mutex

 Run Task 1: keep doing work, release mutex

Ответы [ 2 ]

3 голосов
/ 09 апреля 2020

Это должно быть хорошо, если вы не используете async / await внутри критической секции. Это связано с тем, что никакая другая задача не может выполняться в потоке до тех пор, пока он не завершит выполнение своей текущей задачи.

Если вы используете ожидание внутри критических секций, может возникнуть проблема, поскольку «ожидание» может завершить выполнение настолько, насколько это возможно. нить обеспокоена. Простым решением было бы не использовать await внутри критической секции, и это, вероятно, хорошая идея в целом. Если у вас есть какая-то конкретная c проблема с этим, было бы неплохо опубликовать ее как новый вопрос.

0 голосов
/ 09 апреля 2020

Таким образом, вы обеспокоены тем, что нижеприведенная программа будет содержать ошибки и не сообщит правильное конечное значение 1 000 000 для счетчика:

using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

public class Program
{
    public static void Main()
    {
        const int TASKS_COUNT = 1000;
        const int LOOPS_COUNT = 1000;
        ThreadPool.SetMinThreads(100, 10); // Ensure that we have more threads than cores
        var locker = new object();
        var counter = 0;
        var tasks = Enumerable.Range(1, TASKS_COUNT).Select(async x =>
        {
            for (int i = 0; i < LOOPS_COUNT; i++)
            {
                await Task.Yield();
                lock (locker)
                {
                    counter++;
                }
            }
        }).ToArray();
        Task.WaitAll(tasks);
        Console.WriteLine($"Counter: {counter:#,0}");
    }
}

Вывод:

Счетчик: 1 000 000

Причина правильности этой программы заключается в том, что поток ThreadPool прерывается операционной системой в середине вычисления не -atomi c counter++, она возобновится с тем же вычислением, когда получит следующий временной интервал от операционной системы. TaskScheduler не запланирует выполнение другой задачи в том же потоке до завершения предыдущей задачи, выполняющейся в этом потоке.

Стоит отметить, что с точки зрения TaskScheduler, каждый путь кода между двумя операторами await составляет отдельный мини Task. Разделение выполняется с помощью конечного автомата asyn c. В примере программы свыше 1000 задач создаются явно, но фактическое количество задач, созданных конечным автоматом asyn c, составляет в общей сложности 1 000 000. Все эти задачи были запланированы с помощью метода TaskScheduler.Current.QueueTask.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...