Семафоры

В .NET Framework семафоры поддерживаются с помощью класса System.Threading.Semaphore. Они служат для того, чтобы обеспечить возможность для определенного числа потоков одновременно использовать ресурсы. Всякий раз, когда поток обращается к семафору через WaitOne (или любой другой из методов Wait. . . класса WaitHandle, которые описаны ниже), счетчик семафора декрементируется.

Когда поток-владелец вызывает Release, счетчик инкрементируется. Если поток пытается обратиться к семафору, когда счетчик равен нулю, он блокируется до тех пор, пока другой поток не вызовет Release.

Точно так же, как и с Mutex, при создании семафора можно указывать или не указывать имя, по которому другие процессы будут идентифицировать его.

Если создается семафор без имени, то получается локальный семафор, который доступен только в пределах одного процесса. В любом случае лежащая в основе реализация использует семафор — объект Win32 ядра. Поэтому он является очень тяжеловесным объектом синхронизации, медленным и неэффективным. Если не нужно синхронизировать доступ между несколькими процессами по причинам безопасности, то следует отдавать предпочтение локальным семафорам перед именованными.

Обратите внимание, что поток может запрашивать семафор много раз. Однако он или какой-то другой поток должен вызывать Release соответствующее количество раз, чтобы восстановить счетчик доступности семафора. Задача обеспечения соответствия между вызовами методов Wait. . . и последующими вызовами Release полностью возлатается на разработчика. Ничто не препятствует излишним вызовам Release.

В этом случае, когда другой поток позднее обратится к Release, он может тем самым попытаться вытолкнуть счетчик за пределы допустимого лимита значений, что приведет к генерации исключения SemaphoreFullException. Такие ошибки очень трудно обнаружить, потому что точка, в которой возникла причина ошибки, находится далеко от точки, где она проявляется.

Ранее в разделе “Класс Monitor” был представлен несовершенный пул потоков по имени CrudeThreadPool, и было указано, почему Monitor не является лучшим механизмом синхронизации, который можно использовать для реализации CrudeThreadPool. Ниже приведен несколько модифицированный класс CrudeThreadPool, где для синхронизации применяется Semaphore.

Код класса CrudeThreadPool служит только в качестве примера. В реальных приложениях следует отдавать предпочтение системному пулу потоков, который будет описан ниже.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
using System;
using System.Threading;
using System.Collections;
public class CrudeThreadPool
{
static readonly int MaxWorkThreads = 4; 
static readonly int WaitTimeout = 2000; 
public delegate void WorkDelegate ();
public CrudeThreadPool ()  { 
stop = false;
semaphore = new Semaphore( 0, int.MaxValue ) ;
workQueue = new Queue ();
threads = new Thread[ MaxWorkThreads ];
for( int i = 0; 
i < MaxWorkThreads; ++i )  {
threads[i] =
new Thread( new ThreadStart(this.ThreadFunc)  );
threads[i].Start ();
}
private void ThreadFunc()  { 
do {
if ( !stop )  {
WorkDelegate workltem = null;
if ( semaphore.WaitOne (WaitTimeout) ) {
// Обработать элемент из начала очереди. 
lock( workQueue )  { 
workltem =
(WorkDelegate) workQueue.Dequeue();
}
workltem();
}
} while ( !stop ) ;
}
public void SubmitWorkltem ( WorkDelegate item )  { 
lock( workQueue )  {
workQueue.Enqueue( item );
}
semaphore.Release();
public void Shutdown()  { 
stop = true;
private
private private
Semaphore
Queue Thread[]
semaphore;
workQueue; threads;
private   volatile bool   stop;
}
public class EntryPoint {
static void WorkFunction()  {
Console.WriteLine( "Метод WorkFunction() вызван на потоке {0}",
static void Main()  {
CrudeThreadPool pool = new CrudeThreadPool(); 
for( int i = 0; 
i < 10; 
+ + i )  { 
pool.SubmitWorkltem(
new CrudeThreadPool.WorkDelegate(
EntryPoint.WorkFunction)  ) ;
}
// Задержка для эмуляции выполнения данным потоком другой работы. 
Thread.Sleep ( 1000 ) ; 
pool.Shutdown();

В коде выделены места использования Semaphore.Release для указания, что элемент находится в очереди. Вызов Release инкрементирует счетчик Semaphore, в то время как рабочий поток, успешно завершающий вызов WaitOne, декрементирует этот счетчик. За счет использования Semaphore класс CrudeThreadPool не потеряет рабочих элементов, если они помещены в очередь до запуска потоков.

Semaphore не может выходить за пределы значения Int32.MaxValue; однако если в очереди находится слишком много элементов при достаточном объеме памяти для их поддержки, то это свидетельствует о низкой эффективности обработки в каком-то другом месте.

Вы можете следить за любыми ответами на эту запись через RSS 2.0 ленту. Вы можете оставить ответ, или trackback с вашего собственного сайта.

Оставьте отзыв

XHTML: Вы можете использовать следующие теги: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

 
Rambler's Top100