Как сделать метод потокобезопасным [закрыто]

Предположим, у нас есть такой метод

public static void method(string param)
{
    ** critical section **
    // There are a lot of methods calls 
    // switch cases 
    // if conditions 
    // read and write in dictionary 
    // new class initiations
    ** critical section **  
}

, как мы можем сделать его потокобезопасным, когда происходят тысячи одновременных вызовов?

Могут ли помочь делегаты? Я прочитал здесь , что

Событие изменения не является поточно-ориентированным, но вызов делегата является поточно-ориентированным. Поскольку тип Delegate является неизменным, он безопасен для потоков.

Означает ли это, что делегаты делают мой поток кода безопасным?

Вот несколько понятий:

Diagram

  • на приведенной выше схеме, как правильно использовать Locker? Внутри метода или вне его? почему?
  • Является ли Lock или Mutex быстрее?
  • Когда delegate вступают в игру для безопасности потоков?
  • Как часть общего ресурса определен код?
  • Имеет ли Visual Studio возможность анализировать, где совместно используются ресурсы, и нужно ли сделать потокобезопасным?

  • Как сделать полученную блокировку Поток, чтобы снять блокировку через 2,5 секунды и поставить в очередь все остальные потоки, которым нужна блокировка?

-11
задан Mr.AF 26 July 2019 в 15:52
поделиться

2 ответа

Читайте для окончания, Вы будете наслаждаться им.

Блокировка или Взаимное исключение быстрее?

using System;
using System.Diagnostics;
using System.Threading;

namespace LockingTest
{
    class Program
    {
        public static object locker = new object();
        public static Mutex mutex = new Mutex();
        public static ManualResetEvent manualResetEvent = new ManualResetEvent(false);
        static void Main(string[] args)
        {
            Stopwatch sw = new Stopwatch();
            sw.Restart();
            for (int i = 0; i < 10000000; i++)
            {
                mutex.WaitOne(); // we are testing mutex time overhead
                mutex.ReleaseMutex();
            }
            sw.Stop();
            Console.WriteLine("Mutex :" + "  proccess time token " + sw.Elapsed.ToString() + " miliseconds");
            Thread.Sleep(1000); // let os to be idle 
            sw.Restart();
            for (int i = 0; i < 10000000; i++)
            {
                lock (locker) { } // we are testing lock time overhead
            }
            sw.Stop();
            Console.WriteLine("Lock :" + "  proccess time token " + sw.Elapsed.ToString() + " miliseconds");           
            Console.ReadLine();
        }
    }
}

, если Вы копируете и вставляете выше кода в визуальном stuido и выполняете его, Вы будете видеть Lock_vs_mutex

, как Вы видите lock, 50x быстрее, чем mutex

, Как часть совместно используемого ресурса кода определена?

Visual Studio имеет способность проанализировать, где ресурсы совместно используются и должны быть сделаны ориентированными на многопотоковое исполнение?

, конечно, ДА , я обновил свой visual studio 2010 к [1 131] 2015 , в Visual Studio 2015, когда Вы смотрите верхняя часть каждого метода, Вы будете видеть ссылки взгляд ниже изображения. enter image description here > Да, если бы Visual Studio могла для разговора, она сказала бы мне" , ДЕЛАЮТ ЭТОТ МЕТОД БЕЗОПАСНЫМ " .maybe в следующих обновлениях Visual Studio, они действительно добавляют эту опцию. кто знает?

, Когда ссылки на метод идет высоко, опасность повреждения памяти идет высоко и наоборот.

, Как заставить полученную блокировку распараллелить для выпуска блокировки после 2,5 секунд и очереди все другие потоки, которым нужна блокировка?

using System;
    using System.Diagnostics;
    using System.Threading;
    using System.Threading.Tasks;
    namespace LockReleaseTest
    {
        class Program
        {
            public static object locker = new object();
            public static ManualResetEvent mre = new ManualResetEvent(false);
            public static bool isWorkDone = false;
            public class StateObject
            {
                public int ThreadNumber;
                public string Criticla_Parameter;
                public int ItTakes = 1000;
            }
            static void Main(string[] args)
            {
                for (int i = 0; i < 5; i++)
                {
                    StateObject state = new StateObject();
                    state.ThreadNumber = i;
                    state.Criticla_Parameter = "critical " + i.ToString();
                    ThreadPool.QueueUserWorkItem(method, state);
                }
                Thread.Sleep(13000); // wait previous process to be done
                Console.WriteLine("In order to test release lock after 2.5 sec press enter");
                Console.ReadLine();
                for (int i = 0; i < 5; i++)
                {
                    StateObject state = new StateObject();
                    state.ThreadNumber = i;
                    state.ItTakes = (i + 1) * (1000);
                    state.Criticla_Parameter = "critical " + i.ToString();
                    ThreadPool.QueueUserWorkItem(method2, state);
                }
                Console.ReadLine();
            }
            public static void method(Object state)
            {
                lock (locker)
                {
                    // critcal section
                    string result = ((StateObject)state).Criticla_Parameter;
                    int ThreadNumber = ((StateObject)state).ThreadNumber;
                    Console.WriteLine("Thread " + ThreadNumber.ToString() + " entered");
                    // simultation of process            
                    Thread.Sleep(2000);
                    Console.WriteLine("ThreadNumber is " + ThreadNumber + " Result of proccess : " + result);
                    // critcal section
                }
            }
            public static void method2(Object state)
            {
                if (Monitor.TryEnter(locker, -1))
                {
                    mre.Reset();
                    ThreadPool.QueueUserWorkItem(criticalWork, state);
                    Thread.Sleep(200);
                    ThreadPool.QueueUserWorkItem(LockReleaser, ((StateObject)state).ThreadNumber);
                    mre.WaitOne();
                    Monitor.Exit(locker);
                }
            }
            public static void criticalWork(Object state)
            {
                isWorkDone = false;
                string result = ((StateObject)state).Criticla_Parameter;
                int ThreadNumber = ((StateObject)state).ThreadNumber;
                int HowMuchItTake = ((StateObject)state).ItTakes;
                // critcal section
                Console.WriteLine("Thread " + ThreadNumber.ToString() + " entered");
                // simultation of process            
                Thread.Sleep(HowMuchItTake);
                Console.WriteLine("ThreadNumber " + ThreadNumber + " work done. critical parameter is : " + result);
                isWorkDone = true;
                mre.Set();
                // critcal section
            }
            public static void LockReleaser(Object ThreadNumber)
            {
                Stopwatch sw = new Stopwatch();
                sw.Restart();
                do
                {
                    if (isWorkDone) return; // when work is done don't release lock // continue normal
                } while (sw.Elapsed.Seconds <= 2.5); // timer in order to release lock
                if (!isWorkDone) // more than 2.5 sec time took but work was not done
                {
                    Console.WriteLine("ThreadNumber " + ThreadNumber + " work NOT done. Lock must be released ");
                    mre.Set();
                }
            }
        }
    }

, Если Вы копируете и вставляете вышеупомянутый код в Visual Studio и выполняете его, Вы доберетесь, результат похож на это

Release lock program result

, Как Вы видите в первых процессах, мы не выпускаем блокировку, и все потоки входит последовательно в критический раздел, но во втором процессе мы действительно выпускаем блокировку, когда процесс идет долгое время и когда блокировка выпущена, следующий поток (Поток 2) вводит и получает блокировку. Поскольку, блокировка должна быть выпущена в родительском потоке тогда, мы используем ManualEventRest, чтобы сигнализировать, чтобы родитель выпустил блокировку. я попробовал другие подходы, но они не работали, и исключение SynchronizationLockException происходит.This лучший подход, который я нашел, не выдавая исключение.

, Если это сообщение полезно, не забывают признавать up.sincerely Ваш

2
ответ дан 9 September 2019 в 21:09
поделиться

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

Однако не имеет смысла отменять блокировку (с "внешней стороны" блокировки), не отменяя работу, которая делается в блокировке. Если Вы не отменяете работу, которая делается в блокировке тогда, она может делать попытку продолженного доступа к дефицитному ресурсу, приводящему к двум потокам с помощью ресурса одновременно. Что нужно сделать, вместо того, чтобы повредить блокировку снаружи, нужно отменить сделанную работу, который тогда приведет к блокировке, вышедшей тем рабочим.

комментарий А по поводу поточной обработки и отмены. Не нужно прерывать потоки, потому что в целом это оставляет программу (например, ресурсы сохраненный тем потоком) в неопределенном состоянии. Это были много лет, с тех пор как задачи и отмена задачи были представлены. Задачей является по существу операция или метод, который ставится в очередь, чтобы быть выполненным, наряду с другими Задачами, на потоках, полученных из, например, пул потоков. В эти дни в значительной степени весь недавний код должен быть основан на задачах и следовать за совместным подходом отмены задачи. Следующий код показывает, как сделать это, включая запуск задачи на пуле потоков.

Примечание я использую класс MethodLock, представленный в моем более раннем ответе; это - просто обертка для SemaphoreSlim.

Вот класс Рабочего, который делает некоторую работу с дефицитным ресурсом (и некоторые без ресурса). Это сотрудничает в отмене путем тестирования CancellationToken время от времени. Если отмену требовали тогда, рабочий отменяет себя путем выдачи специального исключения.

        public class Worker
        {
            public Worker(int workerId, CancellationToken ct, int howMuchWorkToDo)
            {
                this.WorkerId = workerId;
                this.CancellationToken = ct;
                this.ToDo = howMuchWorkToDo;
                this.Done = 0;
            }
            public int WorkerId { get; }
            public CancellationToken CancellationToken { get; }
            public int ToDo { get; }
            public int Done { get; set; }

            static MethodLock MethodLock { get; } = new MethodLock();

            public async Task DoWorkAwareAsync()
            {
                this.CancellationToken.ThrowIfCancellationRequested();
                this.Done = 0;
                while (this.Done < this.ToDo) {
                    await this.UseCriticalResourceAsync();
                    await this.OtherWorkAsync();
                    this.CancellationToken.ThrowIfCancellationRequested();
                    this.Done += 1;
                }
                Console.WriteLine($"Worker {this.WorkerId} completed {this.Done} out of {this.ToDo}");
            }

            private async Task UseCriticalResourceAsync()
            {
                using (await MethodLock.LockAsync()) {
                    //Console.WriteLine($"Worker {this.WorkerId} acquired lock on critical resource.");
                    await Task.Delay(TimeSpan.FromMilliseconds(50));
                }
            }
            private async Task OtherWorkAsync()
            {
                await Task.Delay(TimeSpan.FromMilliseconds(50));
            }
        }

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

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

        static void Main(string[] args)
        {
            Random rand = new Random( DateTime.Now.Millisecond);

            Console.WriteLine("---- Cancellation-aware work");
            Task[] tasks = new Task[10];
            for (int i = 0; i < 10; i++) {
                CancellationTokenSource cts = new CancellationTokenSource();
                cts.CancelAfter(TimeSpan.FromMilliseconds(2000));
                int howMuchWork = (rand.Next() % 10) + 1;
                Worker w = new Worker(i, cts.Token, howMuchWork);
                tasks[i] = Task.Run(
                    async () => {
                        try {
                            await w.DoWorkAwareAsync();
                        } catch (OperationCanceledException) {
                            Console.WriteLine($"Canceled worker {w.WorkerId}, work done was {w.Done} out of {w.ToDo}");
                        }
                    },
                    cts.Token
                );
            }
            try {
                Task.WaitAll(tasks);
            } catch (AggregateException ae) {
                foreach (Exception e in ae.InnerExceptions) {
                    Console.WriteLine($"Exception occurred during work: {e.Message}");
                }
            }
            Console.ReadKey();
        }

я прокомментировал бы что присутствие "cts. Маркер" как второй аргумент Задаче. Выполненный НЕ касается принудительного / трудная отмена задачи, созданной Задачей. Выполненный метод. Вся та Задача. Выполненный делает с этим вторым аргументом, сравнивают его с маркером отмены в исключении отмены, и если это - то же тогда Задача. Выполненный переходит задача к Отмененному состоянию.

при выполнении этого, Вы будете видеть что-то как следующее:

    ---- Cancellation-aware work
    Worker 5 completed 1 out of 1
    Worker 2 completed 1 out of 1
    Worker 8 completed 1 out of 1
    Worker 6 completed 3 out of 3
    Worker 7 completed 3 out of 3
    Canceled worker 3, work done was 4 out of 5
    Canceled worker 4, work done was 4 out of 10
    Canceled worker 1, work done was 4 out of 8
    Canceled worker 9, work done was 4 out of 7
    Canceled worker 0, work done was 5 out of 9

Снова, этот дизайн предполагает, что методы рабочего сотрудничают с отменой. Если Вы работаете с унаследованным кодом, где операция рабочего не сотрудничает в прислушивании к запросам отмены тогда, может быть необходимо создать поток для той операции рабочего. Это требует надлежащей очистки, и кроме того она может создать проблемы производительности, потому что она израсходовала потоки, которые являются ограниченным ресурсом. Ответ Simon Mourier посреди этого связанного обсуждения показывает, как сделать это: это возможный прервать Задачу как прерывание Потока (Поток. Метод аварийного прекращения работы)?

1
ответ дан 9 September 2019 в 21:09
поделиться
Другие вопросы по тегам:

Похожие вопросы: