Я пытаюсь записать ThreadManager для своего приложения C#. Я создаю несколько потоков:
Один поток для моего класса для записи текста.
Один поток, который контролирует некоторую статистику.
Несколько потоков для выполнения большой последовательности вычислений (до 4 потоков на ядро и я запускаю свое приложение на 2x четырехъядерный сервер).
Мое приложение обычно работает в течение максимум 24 часов за один раз, таким образом, все потоки создаются в начале, и они сохраняют в течение всего времени выполнение приложения.
Я хочу иметь единственное место, где я "регистрирую" все свои шаги и когда приложение закрывается, я просто вызываю метод, и оно проходит все зарегистрированные потоки и завершает работу их.
С этой целью я создал следующий класс:
public class ThreadManager
{
private static Object _sync = new Object();
private static ThreadManager _instance = null;
private static List<Thread> _threads;
private ThreadManager()
{
_threads = new List<Thread>();
}
public static ThreadManager Instance
{
get
{
lock (_sync)
{
if (_instance == null)
{
_instance = new ThreadManager();
}
}
return _instance;
}
}
public void AddThread(Thread t)
{
lock (_sync)
{
_threads.Add(t);
}
}
public void Shutdown()
{
lock (_sync)
{
foreach (Thread t in _threads)
{
t.Abort(); // does this also abort threads that are currently blocking?
}
}
}
}
Я хочу удостовериться, что все мои потоки уничтожаются так, приложение может закрыться правильно, и закрывающийся посреди некоторого вычисления очень хорошо также. Я должен знать о чем-нибудь здесь? Действительно ли этот подход хорош, учитывая мою ситуацию?
Если вы установите потоки как фоновые, они будут убиты при закрытии приложения.
myThread.IsBackground = true;
Очевидно, если вам нужно, чтобы потоки завершались до выключения, это не то решение, которое вам нужно.
Что, если AddThread вызывается во время завершения работы?
Когда завершение работы завершается, поток, ожидающий в AddThread, добавит новый поток в коллекцию. Это могло привести к зависанию вашего приложения.
Для защиты от этого добавьте флаг bool, который вы когда-либо устанавливали только в Shutdown.
bool shouldGoAway = false;
public void AddThread(Thread t)
{
lock (_sync)
{
if( ! shouldGoAway )
_threads.Add(t);
}
}
public void Shutdown()
{
lock (_sync)
{
shouldGoAway = true;
foreach (Thread t in _threads)
{
t.Abort(); // does this also abort threads that are currently blocking?
}
}
Также вам не следует использовать статические члены - для этого нет причин, поскольку у вас есть экземпляр Singleton.
.Abort () не прерывает потоки, которые блокируются в неуправляемом пространстве. Поэтому, если вы это сделаете, вам нужно использовать какой-то другой механизм.
Прерывание потоков - это то, что вы делаете , когда все остальное терпит неудачу . Это опасный поступок, к которому следует прибегать только в крайнем случае. Правильный способ сделать это - сделать логику потоковой передачи так, чтобы каждый рабочий поток реагировал быстро и правильно, когда основной поток дает ему команду на завершение работы.
По совпадению, это тема моего блога на этой неделе.
http://blogs.msdn.com/ericlippert/archive/2010/02/22/should-i-specify-a-timeout.aspx
Единственный конкретный вопрос, о котором я знаю, это этот: http://www.bluebytesoftware.com/blog/2007/01/30/MonitorEnterThreadAbortsAndOrphanedLocks.aspx
Но я бы не стал прибегать к подобной конструкции. Вы можете заставить каждый из ваших потоков регулярно проверять некоторый флаг, что пришло время завершить работу, а при завершении работы установить этот флаг и дождаться завершения работы всех потоков (с помощью Join()). Это больше похоже на контролируемое завершение работы.
Вы хотите отложенную отмену потоков, что в основном означает, что потоки завершаются сами по себе, в отличие от асинхронной отмены потоков менеджером потоков, что гораздо более неопределенно и опасно.
Если вы хотите обрабатывать отмену потока более элегантно, чем немедленное завершение, вы можете использовать обработчики сигналов, которые запускаются событиями вне потока - возможно, менеджером потоков.
Что делать, если метод AddThread вызывается во время завершения работы?
После завершения завершения работы поток, ожидающий в AddThread, добавит новый поток в коллекцию. Это может привести к зависанию в приложении.
Добавьте флаг bool, который вы когда-либо устанавливали только в Shutdown, чтобы защитить от этого.
bool shouldGoAway = false;
public void AddThread(Thread t)
{
lock (_sync)
{
if( ! shouldGoAway )
_threads.Add(t);
}
}
public void Shutdown()
{
lock (_sync)
{
shouldGoAway = true;
foreach (Thread t in _threads)
{
t.Abort(); // does this also abort threads that are currently blocking?
}
}
Также не следует использовать статические члены - для этого нет причин, поскольку у вас есть экземпляр Singleton.
.Abort () не прерывает потоки, блокирующие неуправляемое пространство. Так что если вы делаете это, вам нужно использовать какой-то другой механизм.
-121--3438925-Короткий ответ: да, следующий метод безопасен для потока:
public static int CalcuateSomething( int number ) {
int i = number * 10;
return i;
}
Все локальные переменные безопасны, если они не указывают на общий объект в куче.
Как и в случае приложений с одним потоком, следующие вызовы этого метода вернут различные значения.
CalculateSomething( 10 ) => 100
CalculateSomething( 20 ) => 200
Почему это так? Потому что каждый вызов номера метода принимает разные значения, и поэтому я тоже буду. Значение i не запоминается после завершения функции, так как i выделяется в стеке. Почти на всех языках функции моделируются в стеке. Каждый раз при вызове другого метода текущий метод приостанавливается. Новый метод вставляется в стек вызовов и вызывается. Когда этот метод завершен, программа отменяет вызов метода и возобновляет его в том месте, где он был остановлен (т.е. в returnAddress). Любая локальная переменная, определенная в этом методе, отделена от стекового кадра этого метода. Стэкфрейм для нашего метода можно было бы представить так:
public Class StackFrameForCalculateSomething implements StackFrame {
public int ReturnAddress;
public int i = 0;
public int number;
}
Стек можно было бы рассматривать как совокупность объектов StackFrame.
Stack callStack
Каждый раз при вызове нового метода программа может выполнять следующее:
StackFrameForCalculcateSomething s = new StackFrameForCalculateSomething();
s.returnAddress = instructionPointer;
s.number = 10;
callStack.push( s );
s.invoke();
StackFrameForCalculcateSomething s2 = new StackFrameForCalculateSomething();
s2.returnAddress = instructionPointer;
s2.number = 20;
callStack.push( s2 );
s2.invoke();
Что это означает для многопоточности? В случае потоков у вас будет несколько независимых callStacks с их собственной коллекцией. Причина, по которой доступ к локальным переменным безопасен, заключается в том, что для callStack потока 1 нет пути получить доступ к callStack потока 2, поскольку они являются отдельными объектами. Так же, как в случае одиночной резьбы s и s2, это различные объекты с различными значениями числа. И поэтому они независимы друг от друга. Рассмотрим, что s было в потоке 1, а s2 является потоком 2. Потоки 1 и 2 не имеют общей памяти, поэтому они безопасны.
Потоки не используют общие кадры стека. Они делят кучу.
-121--3238833-Если вас не волнует состояние рабочего потока, вы можете выполнить _thread и прервать:
void DieDieDie()
{
foreach (Thread thread in _thread)
{
thread.Abort();
thread.Join(); // if you need to wait for the thread to die
}
}
В вашем случае вы, вероятно, можете просто прервать их все и завершить работу, так как они просто делают расчеты. Но если нужно дождаться операции записи в базу данных или закрыть неуправляемый ресурс, то нужно либо поймать ThreadAbortException, либо послать сигнал потокам, чтобы изящно покончить с собой.