Files
OS/lab1/banker.cpp
2025-04-24 23:59:44 +08:00

215 lines
6.2 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};
// BEGIN_LST_CUS_Q
std::mutex customer_q_mtx;
std::deque<Customer> customer_q;
// END_LST_CUS_Q
// BEGIN_LST_COUNT_SEM
std::counting_semaphore<100> customer_num_sem{0};
// END_LST_COUNT_SEM
void signal_handler(int signum) {
// std::cout << "\nReceived signal " << signum << ", stopping...\n";
write(STDOUT_FILENO, "\nCaught SIGINT, shutting down...\n", 33);
bank_open = false;
}
// BEGIN_LST_TELLER
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;
}
// END_LST_TELLER
// BEGIN_LST_CUSTOMER
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();
}
// END_LST_CUSTOMER
// BEGIN_LST_MAIN
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;
}
// END_LST_MAIN