192 lines
5.8 KiB
C++
192 lines
5.8 KiB
C++
#include <atomic>
|
|
#include <chrono>
|
|
#include <csignal>
|
|
#include <deque>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <mutex>
|
|
#include <semaphore>
|
|
#include <sstream>
|
|
#include <syncstream>
|
|
#include <thread>
|
|
#include <vector>
|
|
|
|
#define TELLER_NUM 3
|
|
#define TIME_GRAN_MSEC 200
|
|
|
|
struct CustomerInfo {
|
|
unsigned int id;
|
|
unsigned int arrival_time;
|
|
unsigned int service_time;
|
|
};
|
|
|
|
struct Customer {
|
|
unsigned int id;
|
|
unsigned int service_time;
|
|
};
|
|
|
|
std::atomic<bool> bank_open{false};
|
|
std::mutex customer_q_mtx;
|
|
std::counting_semaphore<100> customer_num_sem{0};
|
|
|
|
std::deque<Customer> customer_q;
|
|
|
|
void signal_handler(int signum) {
|
|
// std::cout << "\nReceived signal " << signum << ", stopping...\n";
|
|
write(STDOUT_FILENO, "\nCaught SIGINT, shutting down...\n", 33);
|
|
bank_open = false;
|
|
}
|
|
|
|
void teller_thread(
|
|
int id, std::chrono::time_point<std::chrono::steady_clock> start_tick) {
|
|
std::osyncstream(std::cout)
|
|
<< "Teller id " << id << " started" << std::endl;
|
|
std::chrono::time_point curr_tick = start_tick;
|
|
unsigned int tick_count = 0;
|
|
bool is_serving = false;
|
|
std::chrono::time_point<std::chrono::steady_clock> start_serving_time;
|
|
struct Customer curr_customer;
|
|
while (bank_open) {
|
|
if (is_serving) {
|
|
if ((std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
(curr_tick - start_serving_time) / TIME_GRAN_MSEC))
|
|
.count() <= curr_customer.service_time) {
|
|
// We are still serving, do nothing
|
|
std::osyncstream(std::cout) << "Teller id " << id << " is serving customer " << curr_customer.id << " at tick " << tick_count << std::endl;
|
|
goto next_tick;
|
|
} else {
|
|
// We have just finished serving.
|
|
std::osyncstream(std::cout) << "Teller id " << id << " finishied serving customer " << curr_customer.id << " at tick " << tick_count << std::endl;
|
|
is_serving = false;
|
|
}
|
|
}
|
|
|
|
// Try to acquire a new customer
|
|
if (customer_num_sem.try_acquire()) {
|
|
// Successfully acquired a customer
|
|
std::lock_guard<std::mutex> lock(customer_q_mtx);
|
|
if (!customer_q.empty()) {
|
|
curr_customer = customer_q.front();
|
|
customer_q.pop_front();
|
|
|
|
std::osyncstream(std::cout)
|
|
<< "Teller id " << id << " is now serving customer "
|
|
<< curr_customer.id << " for " << curr_customer.service_time
|
|
<< " ticks at tick " << tick_count << std::endl;
|
|
|
|
is_serving = true;
|
|
start_serving_time = curr_tick;
|
|
}
|
|
} else {
|
|
std::osyncstream(std::cout)
|
|
<< "Teller id " << id << " is idle at tick " << tick_count << std::endl;
|
|
}
|
|
|
|
next_tick:
|
|
++tick_count;
|
|
curr_tick += std::chrono::milliseconds(TIME_GRAN_MSEC);
|
|
std::this_thread::sleep_until(curr_tick);
|
|
}
|
|
std::osyncstream(std::cout)
|
|
<< "Teller id " << id << " closing" << std::endl;
|
|
}
|
|
|
|
void customer_thread(
|
|
CustomerInfo info,
|
|
std::chrono::time_point<std::chrono::steady_clock> start_tick) {
|
|
|
|
Customer cus{.id = info.id, .service_time = info.service_time};
|
|
|
|
std::chrono::time_point curr_tick = start_tick;
|
|
unsigned int tick_count = 0;
|
|
std::chrono::time_point target_tick = start_tick + std::chrono::milliseconds(TIME_GRAN_MSEC) * info.arrival_time;
|
|
while (curr_tick < target_tick) {
|
|
if (!bank_open) {
|
|
return;
|
|
}
|
|
++tick_count;
|
|
curr_tick += std::chrono::milliseconds(TIME_GRAN_MSEC);
|
|
std::this_thread::sleep_until(curr_tick);
|
|
}
|
|
|
|
// Wake up and push self into queue, then V(customer_num_sem)
|
|
std::osyncstream(std::cout) << "Customer id " << info.id << " arrived at tick " << tick_count << std::endl;
|
|
{
|
|
std::lock_guard<std::mutex> lock(customer_q_mtx);
|
|
customer_q.push_back(cus);
|
|
}
|
|
customer_num_sem.release();
|
|
}
|
|
|
|
int main(int argc, char *argv[]) {
|
|
if (argc != 2) {
|
|
std::cout << "Invalid commandline arg, expected filename";
|
|
return 1;
|
|
}
|
|
|
|
std::signal(SIGINT, signal_handler);
|
|
|
|
// Read config from file
|
|
std::vector<CustomerInfo> customer_infos;
|
|
|
|
std::ifstream file(argv[1]);
|
|
if (!file.is_open()) {
|
|
std::cerr << "Cannot open file: " << argv[1] << std::endl;
|
|
return 1;
|
|
}
|
|
|
|
std::string line;
|
|
while (std::getline(file, line)) {
|
|
std::istringstream iss(line);
|
|
CustomerInfo cus;
|
|
if (iss >> cus.id >> cus.arrival_time >> cus.service_time) {
|
|
customer_infos.push_back(cus);
|
|
} else {
|
|
std::cerr << "Invalid line: " << line << std::endl;
|
|
}
|
|
}
|
|
|
|
file.close();
|
|
|
|
bank_open = true;
|
|
std::chrono::time_point start_tick = std::chrono::steady_clock::now();
|
|
|
|
// Set up tellers
|
|
std::vector<std::thread> tellers;
|
|
|
|
for (int i = 0; i < TELLER_NUM; ++i) {
|
|
try {
|
|
tellers.emplace_back(teller_thread, i, start_tick);
|
|
}
|
|
catch (const std::system_error &e) {
|
|
std::cerr << "Failed to create teller thread, i = " << i << ": "
|
|
<< e.what() << std::endl;
|
|
}
|
|
}
|
|
|
|
// Set up customers
|
|
std::vector<std::thread> customers;
|
|
for (auto cu : customer_infos) {
|
|
try {
|
|
customers.emplace_back(customer_thread, cu, start_tick);
|
|
} catch (const std::system_error &e) {
|
|
std::cerr << "Failed to create customer thread, id = " << cu.id << ": "
|
|
<< e.what() << std::endl;
|
|
}
|
|
}
|
|
|
|
// Wait for all children to shutdown (by ctrl-c handler)
|
|
for (auto &t: customers) {
|
|
if (t.joinable()) {
|
|
t.join();
|
|
}
|
|
}
|
|
|
|
for (auto &t : tellers) {
|
|
if (t.joinable()) {
|
|
t.join();
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
} |