Я - молодший розробник, який працює над написанням оновлення програмного забезпечення, яке отримує дані від стороннього рішення, зберігає їх у базі даних, а потім обробляє дані для використання іншим стороннім рішенням. Наше програмне забезпечення працює як служба Windows.
Переглядаючи код попередньої версії, я бачу таке:
static Object _workerLocker = new object();
static int _runningWorkers = 0;
int MaxSimultaneousThreads = 5;
foreach(int SomeObject in ListOfObjects)
{
lock (_workerLocker)
{
while (_runningWorkers >= MaxSimultaneousThreads)
{
Monitor.Wait(_workerLocker);
}
}
// check to see if the service has been stopped. If yes, then exit
if (this.IsRunning() == false)
{
break;
}
lock (_workerLocker)
{
_runningWorkers++;
}
ThreadPool.QueueUserWorkItem(SomeMethod, SomeObject);
}
Логіка здається чіткою: зачекайте місця в пулі потоків, переконайтеся, що служба не була зупинена, а потім зробіть лічильник потоків і встановіть чергу. _runningWorkersдекрементируется всередині SomeMethod()всередині lockзаяву , що тоді дзвінки Monitor.Pulse(_workerLocker).
Моє запитання:
чи є якась користь у згрупуванні всього коду в одному lock, наприклад, таким:
static Object _workerLocker = new object();
static int _runningWorkers = 0;
int MaxSimultaneousThreads = 5;
foreach (int SomeObject in ListOfObjects)
{
// Is doing all the work inside a single lock better?
lock (_workerLocker)
{
// wait for room in ThreadPool
while (_runningWorkers >= MaxSimultaneousThreads)
{
Monitor.Wait(_workerLocker);
}
// check to see if the service has been stopped.
if (this.IsRunning())
{
ThreadPool.QueueUserWorkItem(SomeMethod, SomeObject);
_runningWorkers++;
}
else
{
break;
}
}
}
Схоже, це може викликати трохи більше очікування на інші потоки, але тоді, схоже, повторне блокування в одному логічному блоці також буде дещо трудомістким. Однак я новачок у багатопотокових, тому я припускаю, що тут є інші проблеми, про які я не знаю.
Єдине інше місце, де _workerLockerзамикається, - це SomeMethod()лише для того, щоб зменшити декрементацію _runningWorkers, а потім поза межами, foreachщоб дочекатися, коли число _runningWorkersперейде до нуля, перш ніж входити та повертатися.
Дякуємо за будь-яку допомогу.
РЕДАКЦІЯ 4/8/15
Дякуємо @delnan за рекомендацію використовувати семафор. Код стає:
static int MaxSimultaneousThreads = 5;
static Semaphore WorkerSem = new Semaphore(MaxSimultaneousThreads, MaxSimultaneousThreads);
foreach (int SomeObject in ListOfObjects)
{
// wait for an available thread
WorkerSem.WaitOne();
// check if the service has stopped
if (this.IsRunning())
{
ThreadPool.QueueUserWorkItem(SomeMethod, SomeObject);
}
else
{
break;
}
}
WorkerSem.Release()називається всередині SomeMethod().
SomeMethodасинхронно, вищезазначений розділ "блокування" буде залишений до або, принаймні, незабаром після SomeMethodзапуску нового потоку .
Monitor.Wait()- звільнити і знову придбати замок, щоб інший ресурс ( SomeMethodу даному випадку) міг ним користуватися. З іншого боку, SomeMethodотримує замок, зменшує лічильник, а потім викликає, Monitor.Pulse()який повертає замок у відповідний метод. Знову ж таки, це моє власне розуміння.
Monitor.Waitзвільняє замок. Я рекомендую ознайомитися з документами.