Параллельное программирование: Учебное пособие, страница 62

Указатели global и переменные типа sync позволяют осуществить ряд полезных механизмов связи. В качестве примера рассмотрим организацию простой общедоступной очереди, как класса, в котором реализуется канал связи между двумя одновременно выполняющимися задачами – производителем и потребителем данных, снабдив обе задачи указателями на соответствующий объект.

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

Очевидным представлением очереди сообщений в CC++ является список связей, в котором каждая запись содержит сообщение плюс указатель на следующее сообщение.

В тексте 3.6 используется именно этот подход. В примере определяется класс Queue (очередь), который сохраняет указатели на голову и хвост очереди сообщений, представленной структурой в виде списка IntQData. Структура данных, управляемая в соответствии с текстом 3.6 показана на рисунке 3.5.

Текст 3.6. – Определение и использование класса очередь

structIntQData

{                                                           // Элемент списка содержит:

syncintvalue;                   // синхронизирующую переменную

structIntQData *next;   // (т.е. сообщение) и указатель на

}                                                           // следующий элемент списка.

                                //---------------------------//

classQueue

{

public:

void enqueue(int);

int dequeue();

private:

voidQueue()     // Инициализация единственного элемента

{                                                                      //Определение головы и

head = tail = newIntQData;// хвоста элемента очереди.

}

IntQData *head, *tail;                    // Объявление указателей на

}                                                                                 // на голову и хвост очереди.

                                //---------------------------//

voidQueue::enqueue(intmsg)               // Определение функции,

{                                                                                 // добавляющей записи:

tail->next = newIntQData;              // размещение нового

tail->value = msg;                       // элемента списка, состо-

tail = tail->next;                                  // ящего из сообщенияmsg

}                                                                                 // и указателя на хвост

                                //---------------------------//

intQueue::dequeue()                        //Определение функции изъятия

{                                                                      // сообщения из головы очереди:

 intretval = head->value;     // сохранение содержимого головы;

IntQData *newh = head->next; // сохранение указателя на хвост;

deletehead;                                      // удаление в списке старой головы;

head = newh;                                      // передвижка указателя на голову;

returnretval;                                 // возвращение изъятого значения.

}


Рисунок 3.5. Представление класса “Очередь” в виде списка.

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

Порядок выполнения названных двух операций важен. Если расположить операции в ином порядке, а именно:

tail->value = msg;

tail->next  = new IntQData;