Saya adalah pengembang junior yang sedang menulis pembaruan untuk perangkat lunak yang menerima data dari solusi pihak ketiga, menyimpannya dalam database, dan kemudian mengkondisikan data untuk digunakan oleh solusi pihak ketiga lainnya. Perangkat lunak kami berjalan sebagai layanan Windows.
Melihat kode dari versi sebelumnya, saya melihat ini:
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);
}
Logikanya tampak jelas: Tunggu ruang di kumpulan utas, pastikan layanan belum dihentikan, lalu tambahkan penghitung utas dan antrekan pekerjaan. _runningWorkers
di SomeMethod()
dalam dalam lock
pernyataan yang kemudian memanggil Monitor.Pulse(_workerLocker)
.
Pertanyaan saya adalah:
Apakah ada manfaat dalam mengelompokkan semua kode di dalam satu lock
, seperti ini:
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;
}
}
}
Sepertinya, itu mungkin menyebabkan sedikit lebih banyak menunggu utas lainnya, tetapi kemudian sepertinya mengunci berulang kali dalam satu blok logis juga akan agak memakan waktu. Namun, saya baru multi-threading, jadi saya berasumsi bahwa ada masalah lain di sini yang tidak saya sadari.
Satu-satunya tempat lain di mana _workerLocker
terkunci adalah SomeMethod()
, dan hanya untuk tujuan pengurangan _runningWorkers
, dan kemudian di luar foreach
untuk menunggu jumlah _runningWorkers
untuk pergi ke nol sebelum masuk dan kembali.
Terima kasih atas bantuannya.
EDIT 4/8/15
Terima kasih kepada @delnan atas rekomendasi untuk menggunakan semaphore. Kode tersebut menjadi:
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()
disebut di dalam SomeMethod()
.
SomeMethod
sinkron, bagian "kunci" di atas akan dibiarkan sebelum atau setidaknya segera setelah utas baru dengan SomeMethod
mulai berjalan.
Monitor.Wait()
adalah untuk melepaskan dan mendapatkan kembali kunci sehingga sumber daya lain ( SomeMethod
, dalam hal ini) dapat menggunakannya. Di ujung lain, SomeMethod
mendapatkan kunci, mengurangi penghitung, dan kemudian panggilan Monitor.Pulse()
yang mengembalikan kunci ke metode yang dimaksud. Sekali lagi, ini adalah pemahaman saya sendiri.
Monitor.Wait
melepaskan kunci. Saya merekomendasikan untuk melihat dokumen.