Опубликован: 24.04.2009 | Доступ: свободный | Студентов: 1180 / 358 | Оценка: 4.39 / 4.28 | Длительность: 18:45:00
Специальности: Программист
Лекция 8:

Программирование приложений в CE

Проблема Производитель-потребитель

Проблема производитель-потребитель является классической проблемой синхронизации, рассматриваемой во многих курсах и учебниках по ОС1. Выполняются два потока. Один поток занят производством объектов, которые он помещает в общий буфер. Второй поток потребляет объекты из общего буфера. Так как два потока сообща используют глобальные переменные для области буфера, требуется использовать примитивы синхронизации, чтобы избежать беспорядочной работы (вспомните, что доступ к общим переменным должен быть взаимно исключающим в параллельных процессах или потоках). В этом примере для синхронизации используется критический раздел, когда потоки обращаются к общим данным, используемым для области буфера.

Буфер задается как кольцевая буферная область (т.е., конец буфера переходит в его начало). Используются два указателя буфера, один для записи данных в буфер с именем "in", и один для чтения данных из буфера с именем "out". Каждый раз, когда увеличивается один из указателей, используется оператор сравнения по модулю C/C++ "%" для переноса указателей, когда они достигают конца буфера. Также используется счетчик числа объектов в буфере. Производитель увеличивает счетчик, а потребитель уменьшает счетчик. Буфер может заполняться и заставлять производителя ожидать, или он может быть пустым и заставлять ожидать потребителя.

Для моделирования случайного времени, требуемого для производства или потребления каждого объекта буфера, используется функция Sleep. Ее аргумент является случайной задержкой времени, предоставляемой Random, генератором случайных чисел в С. Объект, вставляемый в буфер, является увеличивающимся значением отметки времени, возвращаемой GetTickCount.

Здесь представлена первая попытка решения. Требуется промежуточный шаг, чтобы полностью понять проблемы. Мы исправим некоторые проблемы позже.

// ProCom.cpp : Определяет точку входа для консольного приложения.
// Проблема производитель-потребитель с общим круговым буфером 
//
// Пример, который показывает, как создавать и 
// использовать критические разделы 
//
#include "stdafx.h"
//Функция потока (Thread) 
DWORD WINAPI ConsumerThread (LPVOID lpArg);
// Критический раздел 
CRITICAL_SECTION CriticalSection;
// Общий круговой буфер и указатели 
int count = 0;
int in = 0;
int out = 0;
DWORD Buffer[3];

int _tmain(int argc, TCHAR *argv[], TCHAR *envp[])
{
	HANDLE hThread1;
	DWORD dwThread1ID = 0;
	INT nParameter = 1;
	int i;

	printf(" \n");
    _tprintf(_T("   Producer Consumer example\n"));
	for (i=0; i<4; i++) Buffer[i] = 0;
	InitializeCriticalSection(&CriticalSection);
	hThread1 = CreateThread (NULL, 0, ConsumerThread, (LPVOID)nParameter, 0, &dwThread1ID);

// Производитель
	while (1){
		// Проверка заполнения буфера 
		while (count == 4)
		{
			printf("Buffer Full - Producer Waiting\n");
		};
		// Вставляем новый объект в буфер 
		// Общие глобальные переменные - использование 
//критических разделов 
		EnterCriticalSection (&CriticalSection);
			printf("   producer thread produces new item at
 Buffer[%d]                 \n",in);
			++count;
			Buffer[in] = GetTickCount();
			in = (in + 1) % 4;
		LeaveCriticalSection (&CriticalSection);
	// Случайная задержка для моделирования процесса производства
// нового объекта для буфера 
		Sleep(Random()>>21);
	}
	CloseHandle(hThread1);
	DeleteCriticalSection(&CriticalSection);
    return 0;
}
DWORD WINAPI ConsumerThread (LPVOID lpArg) {
	INT threadnumber = (INT) lpArg;


// Потребитель
	while (1){
			// Проверка пустого буфера 
			while (count == 0)
			{
				printf("Buffer Empty - Consumer Waiting\n");
			};
			// Удаление объекта из буфера 
		      // Общие глобальные переменные - использование 
// критических разделов			
EnterCriticalSection (&CriticalSection);		
				--count;
				printf("   consumer thread consumes item from
    Buffer[%d] with time stamp %d\n",out,Buffer[out]);
				out = (out + 1) % 4;
			LeaveCriticalSection (&CriticalSection);
// Случайная задержка для моделирования процесса 
// потребления объекта буфера 
			Sleep(Random()>>21);
	}
	return 0;
 }

Рисунок 8.7 показывает проблему производитель-потребитель, выполняющуюся в eBox. Случайные временные задержки должны помочь раскрыть проблемы синхронизации. При условии, что синхронизация работает правильно, вы должны всегда видеть увеличивающиеся отметки времени во время выполнения.

Но этот первый пример имеет как минимум две проблемы. Первая, ссылка на глобальную переменную счетчика происходит вне критического раздела. Это нарушает правило взаимного исключения для общих переменных, но перемещение его в критическую область будет блокировать потоки. Вторая, циклы while могут потреблять много процессорного времени и энергии. Было бы лучше блокировать потоки.

С помощью трех семафоров можно разрешить эти две проблемы во второй версии этой программы. Один из семафоров используется для взаимного исключения и занимает место критических разделов.

Второй и третий семафоры заменяют циклы while, указывая на условия полного и пустого буфера. Значение двух этих семафоров является ключом к пониманию решения. Они требуют различные начальные значения, одно задается как 0, а для второго задается максимальный размер буфера равный 4. Переменная счетчика на самом деле больше не нужна, но ее удобно использовать для печати сообщений о полном и пустом буфере. Поэтому потоки теперь блокируются с помощью семафора вместо прокручивания в цикле while (уменьшая время работы процессора и энергопотребление), и разрешаются возможные проблемы синхронизации со счетчиком.

Пример вывода первого варианта программы производитель-потребитель

увеличить изображение
Рис. 8.7. Пример вывода первого варианта программы производитель-потребитель
// ProCom.cpp : Определяет точку входа для консольного приложения.
// Проблема производитель-потребитель с общим круговым буфером 
//
// Пример, который показывает, как создавать и использовать семафоры
//
#include "stdafx.h"
// Semaphores
static HANDLE mutex_semaphore; // mutual exclusion lock
static HANDLE full_semaphore;  // something is in buffer
static HANDLE empty_semaphore; // buffer has an empty space
// Общий круговой буфер и указатели 
static int in = 0;
static int out = 0;
static int count = 0;
// Общая область буфера 
DWORD Buffer[4];
//Функция потока (Thread) 
DWORD WINAPI ConsumerThread (LPVOID lpArg);

int _tmain(int argc, TCHAR *argv[], TCHAR *envp[])
{
	HANDLE hThread1;
	DWORD dwThread1ID = 0;
	INT nParameter = 1;
	int i;
	printf(" \n");
	// задание семафоров и их начальных и максимальных значений 
    mutex_semaphore = CreateSemaphore(NULL, 1, 1, TEXT("mutex"));  
// 1 для блокировки мьютекса	
    full_semaphore  = CreateSemaphore(NULL, 0, 4, TEXT("full"));   
// 0 объектов в буфере 
    empty_semaphore = CreateSemaphore(NULL, 4, 4, TEXT("empty"));  
// 4	максимальное количество объектов в буфере 
    _tprintf(_T("   Producer Consumer example\n"));
	for (i=0; i<4; i++) Buffer[i] = 0;
	hThread1 = CreateThread (NULL, 0, ConsumerThread, (LPVOID)nParameter, 0, &dwThread1ID);
// Производитель 
	while (1){
			// Ожидание свободного пространства в буфере 
		WaitForSingleObject(empty_semaphore, INFINITE);
			// Общие глобальные переменные - используем мьютекс 
		WaitForSingleObject(mutex_semaphore, INFINITE);
				// Добавляем новый объект в буфер 
			Buffer[in] = GetTickCount();
			count++;
				// Проверяем не полный ли буфер
			if (count >= 4)
			printf("producer thread produces new item at Buffer[%d]  %d  Buffer now Full    \n",in, Buffer[in]);
			else
			printf("producer thread produces new item at Buffer[%d]  %d              \n",in, Buffer[in]);
			in = (in + 1) % 4;			
		ReleaseSemaphore(mutex_semaphore, 1, NULL);
		ReleaseSemaphore(full_semaphore, 1, NULL);
	// Случайная задержка для моделирования процесса производства 
//нового объекта для буфера 
		Sleep(Random()>>21);
	}
	CloseHandle(hThread1);
 	CloseHandle(mutex_semaphore);
	CloseHandle(full_semaphore);
	CloseHandle(empty_semaphore);
   return 0;
}
DWORD WINAPI ConsumerThread (LPVOID lpArg) {
	INT threadnumber = (INT) lpArg;
// Потребитель 
	while (1){
			// Ожидаем объект в буфере 
		WaitForSingleObject(full_semaphore, INFINITE);
		   	// Общие глобальные переменные - используем мьютекс
		WaitForSingleObject(mutex_semaphore, INFINITE);
				count--;
			// Проверяем, не пустой ли буфер 
				if (count == 0)
				printf("consumer thread consumes item from Buffer[%d]  %d  Buffer now Empty\n",out,Buffer[out]);
				else	
				printf("consumer thread consumes item from Buffer[%d]  %d\n",out,Buffer[out]);
			// Удаляем объект из буфера 
				out = (out + 1) % 4;		
		ReleaseSemaphore(mutex_semaphore, 1, NULL);
		ReleaseSemaphore(empty_semaphore, 1, NULL);
	// Случайная задержка для моделирования процесса потребления 
// объекта из буфера 
		Sleep(Random()>>21);
	}
	return 0;
 }
Пример вывода из второго варианта программы производитель-потребитель, использующей семафоры

увеличить изображение
Рис. 8.8. Пример вывода из второго варианта программы производитель-потребитель, использующей семафоры