otsdaq-mu2e-stm  5.02.01
thread_buffer_management_btpool.cpp
1 #include <algorithm>
2 #include <array>
3 #include <atomic>
4 #include <condition_variable>
5 #include <cstring>
6 #include <functional>
7 #include <iostream>
8 #include <memory>
9 #include <mutex>
10 #include <queue>
11 #include <thread>
12 #include <vector>
13 
14 // Data structure representing a buffer with metadata
15 struct DataStruct
16 {
17  std::vector<int16_t> data; // Dynamically sized data buffer
18  size_t size; // Actual size of the data used
19  char metadata[128]; // Fixed-size metadata array
20 
21  DataStruct(size_t buffer_size) : data(buffer_size), size(0)
22  {
23  std::fill(std::begin(metadata), std::end(metadata), '\0');
24  }
25 };
26 
27 // Ring buffer for thread-safe buffer management
28 template<typename T>
29 class RingBuffer
30 {
31  private:
32  std::vector<std::shared_ptr<T>> buffer;
33  size_t head = 0;
34  size_t tail = 0;
35  size_t capacity;
36  std::atomic<size_t> size = 0;
37  std::mutex mutex;
38  std::condition_variable cv_push;
39  std::condition_variable cv_pop;
40  bool stop = false;
41 
42  public:
43  explicit RingBuffer(size_t max_size) : capacity(max_size), buffer(max_size) {}
44 
45  void push(const std::shared_ptr<T>& item)
46  {
47  std::unique_lock<std::mutex> lock(mutex);
48  cv_push.wait(lock, [this]() { return stop || size < capacity; });
49  if(stop)
50  return;
51  buffer[head] = item;
52  head = (head + 1) % capacity;
53  ++size;
54  cv_pop.notify_one();
55  }
56 
57  std::shared_ptr<T> pop()
58  {
59  std::unique_lock<std::mutex> lock(mutex);
60  cv_pop.wait(lock, [this]() { return stop || size > 0; });
61  if(size == 0)
62  return nullptr;
63  auto item = buffer[tail];
64  tail = (tail + 1) % capacity;
65  --size;
66  cv_push.notify_one();
67  return item;
68  }
69 
70  void shutdown()
71  {
72  {
73  std::lock_guard<std::mutex> lock(mutex);
74  stop = true;
75  }
76  cv_push.notify_all();
77  cv_pop.notify_all();
78  }
79 };
80 
81 // Pre-allocated buffer pool for memory reuse
82 class BufferPool
83 {
84  private:
85  std::vector<std::shared_ptr<DataStruct>> pool;
86  std::mutex mutex;
87 
88  public:
89  BufferPool(size_t pool_size, size_t buffer_size)
90  {
91  for(size_t i = 0; i < pool_size; ++i)
92  {
93  pool.emplace_back(std::make_shared<DataStruct>(buffer_size));
94  }
95  }
96 
97  std::shared_ptr<DataStruct> acquire()
98  {
99  std::lock_guard<std::mutex> lock(mutex);
100  if(pool.empty())
101  {
102  return std::make_shared<DataStruct>(60 * 1024 * 1024 / sizeof(int16_t));
103  }
104  else
105  {
106  auto buffer = pool.back();
107  pool.pop_back();
108  return buffer;
109  }
110  }
111 
112  void release(std::shared_ptr<DataStruct> buffer)
113  {
114  std::lock_guard<std::mutex> lock(mutex);
115  pool.push_back(buffer);
116  }
117 };
118 
119 // Thread pool for task management
121 {
122  private:
123  std::vector<std::thread> workers;
124  std::queue<std::function<void()>> tasks;
125  std::mutex mutex;
126  std::condition_variable cv;
127  bool stop = false;
128 
129  public:
130  explicit ThreadPool(size_t thread_count)
131  {
132  for(size_t i = 0; i < thread_count; ++i)
133  {
134  workers.emplace_back([this] {
135  while(true)
136  {
137  std::function<void()> task;
138  {
139  std::unique_lock<std::mutex> lock(mutex);
140  cv.wait(lock, [this]() { return stop || !tasks.empty(); });
141  if(stop && tasks.empty())
142  return;
143  task = std::move(tasks.front());
144  tasks.pop();
145  }
146  task();
147  }
148  });
149  }
150  }
151 
152  void enqueue(std::function<void()> task)
153  {
154  {
155  std::lock_guard<std::mutex> lock(mutex);
156  tasks.emplace(std::move(task));
157  }
158  cv.notify_one();
159  }
160 
161  void shutdown()
162  {
163  {
164  std::lock_guard<std::mutex> lock(mutex);
165  stop = true;
166  }
167  cv.notify_all();
168  for(auto& worker : workers)
169  {
170  worker.join();
171  }
172  }
173 };
174 
175 // Kernel buffer simulation task
176 void kernelBufferTask(RingBuffer<DataStruct>& bufferQueue, BufferPool& pool)
177 {
178  auto buffer = pool.acquire();
179  buffer->size =
180  (rand() % (60 * 1024 * 1024 / sizeof(int16_t))) + 1000; // Simulate variable size
181  std::fill(buffer->data.begin(), buffer->data.begin() + buffer->size, rand() % 100);
182  strncpy(
183  buffer->metadata, "Generated by kernelBufferThread", sizeof(buffer->metadata));
184  bufferQueue.push(buffer);
185 }
186 
187 // General processing task
188 void processingTask(RingBuffer<DataStruct>& inputQueue,
189  RingBuffer<DataStruct>& outputQueue,
190  BufferPool& pool,
191  const char* stage)
192 {
193  auto buffer = inputQueue.pop();
194  if(buffer)
195  {
196  strncat(buffer->metadata,
197  stage,
198  sizeof(buffer->metadata) - strlen(buffer->metadata) - 1);
199  outputQueue.push(buffer);
200  }
201 }
202 
203 int main()
204 {
205  const size_t max_queue_size = 10; // Maximum size for each ring buffer
206  const size_t buffer_pool_size = 20; // Pre-allocated buffer pool size
207  const size_t buffer_size =
208  60 * 1024 * 1024 / sizeof(int16_t); // Buffer size in elements
209  const size_t thread_pool_size = 6; // Number of threads in the pool
210 
211  // Create buffer pool
212  BufferPool pool(buffer_pool_size, buffer_size);
213 
214  // Define ring buffers for each step
215  RingBuffer<DataStruct> kernelBufferQueue(max_queue_size);
216  RingBuffer<DataStruct> checkDataQueue(max_queue_size);
217  RingBuffer<DataStruct> formEventsQueue(max_queue_size);
218  RingBuffer<DataStruct> rawBufferQueue(max_queue_size);
219  RingBuffer<DataStruct> zsBufferQueue(max_queue_size);
220  RingBuffer<DataStruct> mwdBufferQueue(max_queue_size);
221 
222  // Create thread pool
223  ThreadPool threadPool(thread_pool_size);
224 
225  // Enqueue tasks
226  for(int i = 0; i < 100; ++i)
227  {
228  threadPool.enqueue([&]() { kernelBufferTask(kernelBufferQueue, pool); });
229  threadPool.enqueue([&]() {
230  processingTask(kernelBufferQueue, checkDataQueue, pool, " | Checked");
231  });
232  threadPool.enqueue([&]() {
233  processingTask(checkDataQueue, formEventsQueue, pool, " | Events Formed");
234  });
235  threadPool.enqueue([&]() {
236  processingTask(
237  formEventsQueue, rawBufferQueue, pool, " | Algorithm Processed");
238  });
239  threadPool.enqueue([&]() {
240  processingTask(rawBufferQueue, zsBufferQueue, pool, " | ZS Processed");
241  });
242  threadPool.enqueue([&]() {
243  processingTask(zsBufferQueue, mwdBufferQueue, pool, " | MWD Processed");
244  });
245  }
246 
247  // Simulate running for a limited time
248  std::this_thread::sleep_for(std::chrono::seconds(10));
249 
250  // Shutdown thread pool
251  threadPool.shutdown();
252 
253  // Shutdown all queues
254  kernelBufferQueue.shutdown();
255  checkDataQueue.shutdown();
256  formEventsQueue.shutdown();
257  rawBufferQueue.shutdown();
258  zsBufferQueue.shutdown();
259  mwdBufferQueue.shutdown();
260 
261  return 0;
262 }
Definition: data.hh:4