Я - молодший розробник, який працює над написанням оновлення програмного забезпечення, яке отримує дані від стороннього рішення, зберігає їх у базі даних, а потім обробляє дані для використання іншим стороннім рішенням. Наше програмне забезпечення працює як служба 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
звільняє замок. Я рекомендую ознайомитися з документами.