first commit
This commit is contained in:
3
ProducerConsumer/CMakeLists.txt
Normal file
3
ProducerConsumer/CMakeLists.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/Cpp/CMakeLists.txt)
|
||||
37
ProducerConsumer/Cpp/CMakeLists.txt
Normal file
37
ProducerConsumer/Cpp/CMakeLists.txt
Normal file
@@ -0,0 +1,37 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
# Modify only these if one source file!
|
||||
project(CppProducerConsumer)
|
||||
set(CURRENT_PROJECT_CODE_NAME producerconsumer)
|
||||
set(FILE_EXT cpp)
|
||||
# End
|
||||
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
set(CMAKE_C_STANDARD_REQUIRED True)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED True)
|
||||
|
||||
# We want all the warnings and as errors enabled
|
||||
if (MSVC)
|
||||
# warning level 4 and all warnings as errors
|
||||
add_compile_options(/W4 /WX)
|
||||
else()
|
||||
# lots of warnings and all warnings as errors
|
||||
add_compile_options(-Wall -Wextra -pedantic -Werror)
|
||||
|
||||
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||
find_package(Threads REQUIRED)
|
||||
list(APPEND EXTRA_LIBS "Threads::Threads")
|
||||
endif()
|
||||
|
||||
add_executable(${CMAKE_PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR}/${CURRENT_PROJECT_CODE_NAME}.${FILE_EXT})
|
||||
|
||||
target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC
|
||||
${EXTRA_INCLUDES}
|
||||
)
|
||||
|
||||
target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC
|
||||
${EXTRA_LIBS}
|
||||
)
|
||||
|
||||
|
||||
203
ProducerConsumer/Cpp/producerconsumer.cpp
Normal file
203
ProducerConsumer/Cpp/producerconsumer.cpp
Normal file
@@ -0,0 +1,203 @@
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <queue>
|
||||
#include <cstdlib>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <random>
|
||||
#include <memory>
|
||||
|
||||
#ifdef __cpp_lib_semaphore
|
||||
#include <semaphore>
|
||||
using PCSemaphore = std::counting_semaphore<std::numeric_limits<std::ptrdiff_t>::max()>;
|
||||
#else
|
||||
// Simple semaphore since it's not a part of the C++ std
|
||||
class semaphore final
|
||||
{
|
||||
private:
|
||||
std::mutex m_mutex;
|
||||
std::condition_variable m_cv;
|
||||
int m_count;
|
||||
|
||||
public:
|
||||
semaphore(const int& initValue = 0) : m_count(initValue) {};
|
||||
void acquire() // Wait for a signal to unblock the thread
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
while (m_count == 0)
|
||||
{
|
||||
m_cv.wait(lock);
|
||||
}
|
||||
m_count--;
|
||||
}
|
||||
|
||||
void release() // Notify waiters that data is available for access
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
m_count++;
|
||||
m_cv.notify_one();
|
||||
}
|
||||
};
|
||||
using PCSemaphore = semaphore;
|
||||
#endif
|
||||
|
||||
/*
|
||||
Simple producer consumer example.
|
||||
|
||||
N producers write ints into a shared queue (mConsumerData).
|
||||
M consumers read these ints, one at a time, then "sleep" that time to pretend its processing
|
||||
|
||||
Once all the data is done, the threads join and exit. They know based on the global exit flag
|
||||
|
||||
Producers P P P P
|
||||
write to --> mConsumerData -> Post Semaphore --> repeat
|
||||
Consumers C C C C C C
|
||||
wait on semaphore --> pop oldest mConsumerData --> repeat
|
||||
|
||||
The "data" is just an int for how long the consumer will "process" data.
|
||||
|
||||
Essentially, the producer sleeps for some time [min,max] then pushes an int [min,max]
|
||||
into the queue to simulate producing data.
|
||||
The consumers wait on that int then sleep for that time to simulate processing of data.
|
||||
*/
|
||||
|
||||
class ProducerConsumer final
|
||||
{
|
||||
public:
|
||||
struct InitParams
|
||||
{
|
||||
InitParams() {}
|
||||
|
||||
size_t ProduceCount = 100; // How much "data" to produce
|
||||
size_t MinProduceDelayMs = 0; // Min time to produce "data"
|
||||
size_t MaxProduceDelayMs = 100; // Max time to produce "data"
|
||||
size_t MinConsumeDelayMs = 25; // Min time to consume "data"
|
||||
size_t MaxConsumeDelayMs = 1000; // Max time to consume "data"
|
||||
size_t ProducerThreads = 4; // Number of producer threads to start up
|
||||
size_t ConsumerThreads = 10; // Number of consumer threads to start up
|
||||
};
|
||||
|
||||
ProducerConsumer(const InitParams ¶ms = InitParams());
|
||||
void Start();
|
||||
void WaitDone();
|
||||
|
||||
private:
|
||||
void Producer();
|
||||
void Consumer();
|
||||
|
||||
static const int sExitFlag;
|
||||
InitParams mInitParams;
|
||||
std::mutex mListLock;
|
||||
PCSemaphore mSemaphore;
|
||||
std::queue<int> mConsumerData;
|
||||
std::vector<std::thread> mProducers;
|
||||
std::vector<std::thread> mConsumers;
|
||||
std::atomic_uint mProduced;
|
||||
std::atomic_bool mShouldExit;
|
||||
};
|
||||
|
||||
const int ProducerConsumer::sExitFlag = -1;
|
||||
|
||||
ProducerConsumer::ProducerConsumer(const InitParams ¶ms)
|
||||
: mInitParams(params)
|
||||
, mSemaphore(0)
|
||||
, mProduced(0)
|
||||
, mShouldExit(false)
|
||||
{
|
||||
}
|
||||
|
||||
void ProducerConsumer::Start()
|
||||
{
|
||||
mShouldExit = false;
|
||||
|
||||
// Kick off all the consumer threads
|
||||
for (size_t i = 0; i < mInitParams.ConsumerThreads; i++)
|
||||
{
|
||||
mConsumers.push_back(std::thread(&ProducerConsumer::Consumer, this));
|
||||
}
|
||||
|
||||
// Kick off all the producer threads
|
||||
for (size_t i = 0; i < mInitParams.ProducerThreads; i++)
|
||||
{
|
||||
mProducers.push_back(std::thread(&ProducerConsumer::Producer, this));
|
||||
}
|
||||
}
|
||||
|
||||
void ProducerConsumer::WaitDone()
|
||||
{
|
||||
// Wait for all the producers to finish producing and exit
|
||||
for (auto &t : mProducers)
|
||||
{ t.join(); }
|
||||
|
||||
// Push the exit data flag to all the consumer threads
|
||||
mListLock.lock();
|
||||
for (size_t i = 0; i < mInitParams.ConsumerThreads; i++)
|
||||
{
|
||||
mConsumerData.push(sExitFlag);
|
||||
mSemaphore.release();
|
||||
}
|
||||
mListLock.unlock();
|
||||
|
||||
|
||||
std::cout << "Waiting on consumers..." << std::endl;
|
||||
for (auto &t : mConsumers) // Wait for all the consumers to finish and exit
|
||||
{ t.join(); }
|
||||
std::cout << "All consumers done. Exiting now." << std::endl;
|
||||
}
|
||||
|
||||
void ProducerConsumer::Producer()
|
||||
{
|
||||
unsigned int oldVal;
|
||||
std::random_device randDevice;
|
||||
|
||||
while (oldVal = mProduced.fetch_add(1), oldVal < mInitParams.ProduceCount)
|
||||
{
|
||||
// "Produce" data
|
||||
std::cout << "Producing #" << oldVal << std::endl;
|
||||
int delay = int((randDevice() % mInitParams.MaxProduceDelayMs) + mInitParams.MinProduceDelayMs);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(delay));
|
||||
|
||||
// push the "data" onto the queue
|
||||
mListLock.lock();
|
||||
mConsumerData.push(int((randDevice() % mInitParams.MaxConsumeDelayMs) + mInitParams.MinConsumeDelayMs));
|
||||
mListLock.unlock();
|
||||
|
||||
mSemaphore.release();
|
||||
}
|
||||
}
|
||||
|
||||
void ProducerConsumer::Consumer()
|
||||
{
|
||||
int nextData;
|
||||
|
||||
while (true)
|
||||
{
|
||||
mSemaphore.acquire();
|
||||
|
||||
// Pull the next bit of data from the queue
|
||||
mListLock.lock();
|
||||
nextData = mConsumerData.front();
|
||||
mConsumerData.pop();
|
||||
mListLock.unlock();
|
||||
|
||||
// When we pull the exit "data", we just need to break
|
||||
if (nextData == sExitFlag) break;
|
||||
|
||||
// otherwise, "consume" the data
|
||||
std::cout << "Consuming for " << nextData << "ms" << std::endl;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(nextData));
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
ProducerConsumer prod;
|
||||
|
||||
prod.Start();
|
||||
prod.WaitDone();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
9
ProducerConsumer/Rust/producerconsumer.rs
Normal file
9
ProducerConsumer/Rust/producerconsumer.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
fn main() {
|
||||
let shared_queue: VecDeque<i32> = VecDeque::new();
|
||||
|
||||
println!("Hello, World!");
|
||||
}
|
||||
Reference in New Issue
Block a user