如何:實作各種生產者-消費者模式
本主題描述如何在應用程式中實作生產者-取用者模式。 在此模式中,「生產者」會將訊息傳送至訊息區塊,而「消費者」會從該區塊讀取訊息。
本主題示範兩個案例。 在第一個案例中,取用者必須接收產生者傳送的每個訊息。 在第二個案例中,取用者會定期輪詢資料,因此不需要接收每個訊息。
本主題中的這兩個範例都使用代理程式、訊息區塊和訊息傳遞函式,將訊息從產生者傳輸到取用者。 產生者代理程式會 使用並行::send 函式將訊息 寫入並行::ITarget 物件。 取用者代理程式會 使用並行::receive 函式從 並行::ISource 物件讀取訊息。 這兩個代理程式都會保留 sentinel 值,以協調處理結束。
如需非同步代理程式的詳細資訊,請參閱 非同步代理程式 。 如需訊息區塊和訊息傳遞函式的詳細資訊,請參閱 非同步消息塊 和 訊息傳遞函式 。
範例:將數列數位傳送給取用者代理程式
在此範例中,生產者代理程式會將一系列數位傳送給取用者代理程式。 取用者會接收每個數位,並計算其平均值。 應用程式會將平均值寫入主控台。
此範例會使用 並行::unbounded_buffer 物件,讓產生者將訊息排入佇列。 類別 unbounded_buffer
會實作 ITarget
和 ISource
,讓產生者和取用者可以傳送和接收共用緩衝區的訊息。 和 receive
函 send
式會協調將資料從產生者傳播至取用者的工作。
// producer-consumer-average.cpp
// compile with: /EHsc
#include <agents.h>
#include <iostream>
using namespace concurrency;
using namespace std;
// Demonstrates a basic agent that produces values.
class producer_agent : public agent
{
public:
explicit producer_agent(ITarget<int>& target, unsigned int count, int sentinel)
: _target(target)
, _count(count)
, _sentinel(sentinel)
{
}
protected:
void run()
{
// Send the value of each loop iteration to the target buffer.
while (_count > 0)
{
send(_target, static_cast<int>(_count));
--_count;
}
// Send the sentinel value.
send(_target, _sentinel);
// Set the agent to the finished state.
done();
}
private:
// The target buffer to write to.
ITarget<int>& _target;
// The number of values to send.
unsigned int _count;
// The sentinel value, which informs the consumer agent to stop processing.
int _sentinel;
};
// Demonstrates a basic agent that consumes values.
class consumer_agent : public agent
{
public:
explicit consumer_agent(ISource<int>& source, int sentinel)
: _source(source)
, _sentinel(sentinel)
{
}
// Retrieves the average of all received values.
int average()
{
return receive(_average);
}
protected:
void run()
{
// The sum of all values.
int sum = 0;
// The count of values received.
int count = 0;
// Read from the source block until we receive the
// sentinel value.
int n;
while ((n = receive(_source)) != _sentinel)
{
sum += n;
++count;
}
// Write the average to the message buffer.
send(_average, sum / count);
// Set the agent to the finished state.
done();
}
private:
// The source buffer to read from.
ISource<int>& _source;
// The sentinel value, which informs the agent to stop processing.
int _sentinel;
// Holds the average of all received values.
single_assignment<int> _average;
};
int wmain()
{
// Informs the consumer agent to stop processing.
const int sentinel = 0;
// The number of values for the producer agent to send.
const unsigned int count = 100;
// A message buffer that is shared by the agents.
unbounded_buffer<int> buffer;
// Create and start the producer and consumer agents.
producer_agent producer(buffer, count, sentinel);
consumer_agent consumer(buffer, sentinel);
producer.start();
consumer.start();
// Wait for the agents to finish.
agent::wait(&producer);
agent::wait(&consumer);
// Print the average.
wcout << L"The average is " << consumer.average() << L'.' << endl;
}
此範例會產生下列輸出。
The average is 50.
範例:將一系列股票報價傳送給消費者代理程式
在此範例中,生產者代理程式會將一系列股票報價傳送給消費者代理程式。 取用者代理程式會定期讀取目前的引號,並將其列印至主控台。
這個範例與上一個 範例類似,不同之處在于它會使用並行::overwrite_buffer 物件,讓產生者與取用者共用一則訊息。 如同上一個範例, overwrite_buffer
類別會實作 ITarget
和 ISource
,讓產生者和取用者可以在共用訊息緩衝區上採取行動。
// producer-consumer-quotes.cpp
// compile with: /EHsc
#include <agents.h>
#include <array>
#include <algorithm>
#include <iostream>
using namespace concurrency;
using namespace std;
// Demonstrates a basic agent that produces values.
class producer_agent : public agent
{
public:
explicit producer_agent(ITarget<double>& target)
: _target(target)
{
}
protected:
void run()
{
// For illustration, create a predefined array of stock quotes.
// A real-world application would read these from an external source,
// such as a network connection or a database.
array<double, 6> quotes = { 24.44, 24.65, 24.99, 23.76, 22.30, 25.89 };
// Send each quote to the target buffer.
for_each (begin(quotes), end(quotes), [&] (double quote) {
send(_target, quote);
// Pause before sending the next quote.
concurrency::wait(20);
});
// Send a negative value to indicate the end of processing.
send(_target, -1.0);
// Set the agent to the finished state.
done();
}
private:
// The target buffer to write to.
ITarget<double>& _target;
};
// Demonstrates a basic agent that consumes values.
class consumer_agent : public agent
{
public:
explicit consumer_agent(ISource<double>& source)
: _source(source)
{
}
protected:
void run()
{
// Read quotes from the source buffer until we receive
// a negative value.
double quote;
while ((quote = receive(_source)) >= 0.0)
{
// Print the quote.
wcout.setf(ios::fixed);
wcout.precision(2);
wcout << L"Current quote is " << quote << L'.' << endl;
// Pause before reading the next quote.
concurrency::wait(10);
}
// Set the agent to the finished state.
done();
}
private:
// The source buffer to read from.
ISource<double>& _source;
};
int wmain()
{
// A message buffer that is shared by the agents.
overwrite_buffer<double> buffer;
// Create and start the producer and consumer agents.
producer_agent producer(buffer);
consumer_agent consumer(buffer);
producer.start();
consumer.start();
// Wait for the agents to finish.
agent::wait(&producer);
agent::wait(&consumer);
}
此範例會產生下列範例輸出。
Current quote is 24.44.
Current quote is 24.44.
Current quote is 24.65.
Current quote is 24.99.
Current quote is 23.76.
Current quote is 22.30.
Current quote is 25.89.
與 unbounded_buffer
物件不同,函 receive
式不會從 overwrite_buffer
物件中移除訊息。 如果取用者在產生者覆寫該訊息之前多次從訊息緩衝區讀取,接收者每次都會取得相同的訊息。
編譯程式碼
複製範例程式碼,並將其貼到 Visual Studio 專案中,或貼到名為 producer-consumer.cpp
的檔案中,然後在 Visual Studio 命令提示字元視窗中執行下列命令。
cl.exe /EHsc producer-consumer.cpp