Created
September 9, 2022 18:05
-
-
Save jrepp/baac41719a0357a213beb5e72d7e61e2 to your computer and use it in GitHub Desktop.
Example of using a flyweight pattern to lock and protect access to a hardware bus
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <iostream> | |
#include <unordered_map> | |
#include <mutex> | |
#include <thread> | |
#include <vector> | |
#include <atomic> | |
#include <cstdint> | |
#include <cassert> | |
#include <chrono> | |
#include <random> | |
using namespace std; | |
struct bus_address_t { | |
uint32_t bus_id; | |
}; | |
struct device_address_t { | |
uint32_t device_id; | |
}; | |
// forward declare the flyweight | |
class bus_access_t; | |
// the bus manager doesn't provide bus access directdly, instead | |
// users must acquire a bus_access_t flyweight, when it goes | |
// out of scope it automatically releases the bus. | |
class bus_manager_t { | |
public: | |
bus_manager_t(const bus_address_t &address) : m_address(address) {} | |
~bus_manager_t() { assert(m_holders == 0); } | |
bool acquire(bus_access_t &access); | |
protected: | |
friend class bus_access_t; | |
void enter() { | |
m_holders++; | |
} | |
void leave() { | |
m_holders--; | |
} | |
uint32_t read() { | |
m_value = m_value + (m_value & 0xffff) + 1; | |
return m_value; | |
} | |
void write(uint32_t v) { | |
m_value = v << 16; | |
} | |
private: | |
bus_address_t m_address = { 0 }; | |
uint32_t m_value = 0; | |
mutex m_mut; | |
atomic<uint32_t> m_holders; | |
inline bool ensure_locked() { | |
return m_holders == 0; | |
} | |
}; | |
class bus_access_t { | |
public: | |
bus_access_t(bus_manager_t &bus, uint32_t client_id) | |
: m_bus(bus), m_lock(bus.m_mut), m_client_id(client_id) | |
{ | |
bus.enter(); | |
} | |
~bus_access_t() { | |
m_bus.leave(); | |
} | |
// bus access API: | |
uint32_t read() { | |
uint32_t v = m_bus.read(); | |
cout << "client[" << m_client_id << "] READ " << v << endl; | |
return v; | |
} | |
void write(uint32_t v) { | |
cout << "client[" << m_client_id << "] WRITE " << v << endl; | |
m_bus.write(m_client_id); | |
} | |
private: | |
bus_manager_t &m_bus; | |
unique_lock<mutex> m_lock; | |
uint32_t m_client_id; | |
}; | |
void serialize_protocol(bus_access_t &access) { | |
} | |
// To execute C++, please define "int main()" | |
int main() { | |
constexpr uint32_t c_num_threads = 32; | |
random_device r; | |
// Generate a normal distribution around that mean | |
seed_seq seed2{r(), r(), r(), r(), r(), r(), r(), r()}; | |
mt19937 e2(seed2); | |
std::normal_distribution<> normal_dist(5, 2); | |
bus_manager_t mgr(bus_address_t{1}); | |
vector<thread> threads; | |
for(uint32_t i = 0; i < c_num_threads; ++i) { | |
threads.emplace_back([i, &mgr, &e2, &normal_dist]{ | |
cout << "hello from thread " << i << endl; | |
auto sleep_ms = normal_dist(e2); | |
bus_access_t access(mgr, i); | |
for (uint32_t i = 0; i < 100; ++i) { | |
access.read(); | |
access.write(i); | |
access.write(i); | |
access.write(i); | |
} | |
this_thread::sleep_for(chrono::milliseconds((uint64_t)sleep_ms)); | |
cout << "thread finished " << i << endl; | |
}); | |
} | |
for (auto &t : threads) { | |
if (t.joinable()) { | |
t.join(); | |
} | |
} | |
cout << "finished." << endl; | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment