Многопоточность в IronPython
У меня есть класс "script" в IronPython, а скриптинг в моем приложении работает, вызывая методы на его экземпляре. Мне нужно реализовать сценарии вызова из нескольких потоков. Каков правильный способ сделать это?
У меня много проблем:
-
Является ScriptScope
потокобезопасным? Информация противоречива. Документация ScriptScope гласит: "ScriptScope не является потокобезопасным. Хост должен либо заблокировать, когда несколько потоков могут получить доступ к одному модулю, либо сделать копию для каждого нить." Однако IronRuby использует одну и ту же DLR и @JimmySchementi говорит, что "ScriptRuntime, ScriptEngine и ScriptScope все поточно-безопасные, предназначенные для использования между потоками. В частности, ScriptScope использует потокобезопасное хранилище данных, поэтому ScriptScope может совместно использоваться потоками".
-
Если я создам несколько ScriptScope
s, это будет означать выполнение одной и той же инициализации script несколько раз. Предположим, что я запустил десять файлов Python script, импортировал пять сборок и в целом выполнил совсем немного кода, чтобы получить объект "script". Есть ли способ избежать затрат времени и памяти на запуск большого количества одного и того же кода для каждого потока?
-
Создает ли переменная ScriptScope
thread-static (т.е. применяет ThreadStaticAttribute
) и выполняет инициализацию для каждого потока, который используется Task.Run
как идти? Или я должен использовать TaskScheduler
с ограничением на concurrency, потому что стоимость нескольких областей видимости высока?
В целом: как правильно реализовать запуск того же script для разных аргументов в нескольких потоках? Скрипты должны выполняться одновременно и не должны вылетать из-за условий гонки.
Ответы
Ответ 1
1.
Если документация ScriptScope говорит, что она не является потокобезопасной, верьте ей или, по крайней мере, действуйте так, как вы ей верите. @JimmySchementi, возможно, рассмотрела текущую реализацию и выяснила, что она в настоящее время является потокобезопасной, но это не дает никаких гарантий того, как она будет вести себя в следующем патче класса, не говоря уже о следующем крупном выпуске.
2.
Да, вам нужно будет инициализировать каждый из ваших ScriptScopes. Я попытался бы свести к минимуму количество требуемых ScriptScopes, как это сделать будет зависеть от вашей настройки. Если основной целью соответствующих потоков является размещение ScriptScope, тогда вы должны использовать ThreadPool с каждым потоком, имеющим один ThreadLocal <ScriptScope> . Если эти потоки выполняют другие действия, а также запущенные скрипты, тогда у вас должен быть пул объектов, в котором хранятся ScriptScopes, и каждый поток может проверить ScriptScopes, выполнить работу, а затем освободить ScriptScope.
3.
Предпочитайте ThreadLocal через ThreadStatic, если вы идете по этому пути.
Ответ 2
Вот как вы это делаете в сценариях, если я правильно вас понял.
https://github.com/dretax/Python-Plugins/blob/master/PlutonPlugins/PluIRC/PluIRC.py#L154
Посмотрите, что делает этот script, он полностью запускает новый поток одной и той же вещи с разными аргументами.
Файл py, который вы можете увидеть, если FULLY threaded, и использует IronPython.
Это КАК вы правильно делаете потоки, а НЕ каким-либо другим способом.
Ответ 3
Так как это не похоже на то, что вы получаете конкретный ответ, у меня общий.
Один мой коллега использовал IronPhython в моей предыдущей работе и обработке многопоточности было бы очень важно, поэтому я могу сказать, что можно запустить IronPython в многопоточной среде на производственной системе.
Я не знаю, использовал ли он свою собственную блокировку или зависел от блокировки внутри IronPython.
Я бы предложил:
a) Проведите тесты самостоятельно. Вы должны написать простой тест, чтобы убедиться, что он безопасен или нет. Что-то грубое, как следующий код, может быть хорошим началом:
[Test]
public void TwoThreadsWithTheirOwnContexts() {
//Create two threads
var tasks = new Task[2];
tasks[0] = Task.Factory.StartNew(PrintSomethingInIronPython1);
tasks[1] = Task.Factory.StartNew(PrintSomethingInIronPython2);
Task.WaitAll(tasks);
}
b) В любом случае добавьте блокировки. Возможно, не большая проблема с блокировкой в коде, и вы устраните неопределенность.
В конечном счете, если документы говорят, что что-то не является потокобезопасным, вам придется либо доказать, что это (тест), либо играть безопасно (ваша собственная блокировка). Независимо от того, что нужно делать многопоточные тесты, прежде чем вы входите в производство. Я не думаю, что вы действительно теряете время, делая их в начале.
Re 2:
Опять предложение. Создайте потоки пулов, которые инициализируют один раз, а затем повторно используют эти потоки.