Compare commits
5 commits
6793f7f4ea
...
2b3c28d249
| Author | SHA1 | Date | |
|---|---|---|---|
| 2b3c28d249 | |||
| f04d70f615 | |||
| d6ef47cff4 | |||
| aadfd1f899 | |||
| 7562a7a497 |
20 changed files with 728 additions and 17 deletions
3
.clang-format
Normal file
3
.clang-format
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
---
|
||||
BasedOnStyle: LLVM
|
||||
---
|
||||
9
.gitignore
vendored
9
.gitignore
vendored
|
|
@ -54,6 +54,15 @@ dkms.conf
|
|||
# debug information files
|
||||
*.dwo
|
||||
|
||||
# LaTeX
|
||||
.auctex-auto/
|
||||
*.fls
|
||||
*.log
|
||||
*.synctex.gz
|
||||
*.fdb_latexmk
|
||||
*.aux
|
||||
*.pdf
|
||||
|
||||
# Nix related things
|
||||
.direnv/
|
||||
result*/
|
||||
|
|
|
|||
|
|
@ -1,22 +1,22 @@
|
|||
let
|
||||
basic-c = name: { stdenv }:
|
||||
basicC = name: { stdenv }:
|
||||
stdenv.mkDerivation {
|
||||
inherit name;
|
||||
src = ./.;
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
gcc part2/${name}.c -o ${name}
|
||||
gcc part2/${name}.c -o ${name}.bin
|
||||
runHook postBuild
|
||||
'';
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
mkdir -p $out/bin
|
||||
install ${name} $out/bin/
|
||||
install ${name}.bin $out/bin/${name}
|
||||
runHook postInstall
|
||||
'';
|
||||
};
|
||||
in {
|
||||
a1-hello = basic-c "hello";
|
||||
a1-employee = basic-c "employee";
|
||||
a1-logwriter = basic-c "logwriter";
|
||||
a1-hello = basicC "hello";
|
||||
a1-employee = basicC "employee";
|
||||
a1-logwriter = basicC "logwriter";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,26 @@
|
|||
let
|
||||
basic-c = subdir: name: { stdenv }:
|
||||
basicC = subdir: name: { stdenv }:
|
||||
stdenv.mkDerivation {
|
||||
inherit name;
|
||||
src = ./.;
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
gcc ${subdir}/${name}.c -o ${name}
|
||||
gcc ${subdir}/${name}.c -o ${name}.bin
|
||||
runHook postBuild
|
||||
'';
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
mkdir -p $out/bin
|
||||
install ${name} $out/bin/
|
||||
install ${name}.bin $out/bin/${name}
|
||||
runHook postInstall
|
||||
'';
|
||||
};
|
||||
in {
|
||||
a2-p1-producer = basic-c "part1" "producer";
|
||||
a2-p1-consumer = basic-c "part1" "consumer";
|
||||
a2-p1-producer = basicC "part1" "producer";
|
||||
a2-p1-consumer = basicC "part1" "consumer";
|
||||
|
||||
a2-p2-bidirectional = basic-c "part2" "bidirectional";
|
||||
a2-p2-bidirectional = basicC "part2" "bidirectional";
|
||||
|
||||
a2-p3-producer = basic-c "part3" "producer";
|
||||
a2-p3-consumer = basic-c "part3" "consumer";
|
||||
a2-p3-producer = basicC "part3" "producer";
|
||||
a2-p3-consumer = basicC "part3" "consumer";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
subdirs = [
|
||||
"assignment1"
|
||||
"assignment2"
|
||||
"project1"
|
||||
];
|
||||
|
||||
eachSystem = nixpkgs.lib.genAttrs (import systems);
|
||||
|
|
|
|||
25
project1/README.md
Normal file
25
project1/README.md
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# CS3502 Project 1: Asynchronous Banking Account Management
|
||||
|
||||
This directory contains multiple toy models of asynchronous bank account systems, demonstrating the principles of race condition and deadlock avoidance.
|
||||
|
||||
## Usage
|
||||
|
||||
There are 4 programs labeled Phases 1-4:
|
||||
|
||||
- **Phase 1**: Naive non-supervised multi-threaded account system
|
||||
- **Phase 2**: Mutex-based account system
|
||||
- **Phase 3**: Deadlock generation
|
||||
- **Phase 4**: Deadlock resolution via lock ordering
|
||||
|
||||
If you have [Nix](https://nixos.org/) installed:
|
||||
|
||||
```sh
|
||||
nix run .#p1-phase1
|
||||
```
|
||||
|
||||
Otherwise, the code must be compiled manually:
|
||||
|
||||
``` sh
|
||||
gcc phase1/phase1.c -o phase1.bin
|
||||
```
|
||||
|
||||
23
project1/default.nix
Normal file
23
project1/default.nix
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
let
|
||||
basicC = subdir: name: { stdenv }:
|
||||
stdenv.mkDerivation {
|
||||
inherit name;
|
||||
src = ./.;
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
gcc ${subdir}/${name}.c -o ${name}.bin
|
||||
runHook postBuild
|
||||
'';
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
mkdir -p $out/bin
|
||||
install ${name}.bin $out/bin/${name}
|
||||
runHook postInstall
|
||||
'';
|
||||
};
|
||||
in {
|
||||
p1-phase1 = basicC "phase1" "phase1";
|
||||
p1-phase2 = basicC "phase2" "phase2";
|
||||
p1-phase3 = basicC "phase3" "phase3";
|
||||
p1-phase4 = basicC "phase4" "phase4";
|
||||
}
|
||||
BIN
project1/phase1/output.png
Normal file
BIN
project1/phase1/output.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 207 KiB |
107
project1/phase1/phase1.c
Normal file
107
project1/phase1/phase1.c
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define NUM_ACCOUNTS 8
|
||||
#define NUM_TELLERS 128
|
||||
#define TRANSACTIONS_PER_TELLER 100
|
||||
|
||||
// Monetary value in cents
|
||||
typedef long currency;
|
||||
|
||||
void printf_currency(currency c) {
|
||||
if (c < 0) {
|
||||
c = -c;
|
||||
printf("-");
|
||||
}
|
||||
printf("$%ld.%.2ld", c / 100, c % 100);
|
||||
}
|
||||
|
||||
// Accounts (shared resource)
|
||||
typedef struct account {
|
||||
int account_id;
|
||||
currency balance;
|
||||
int transaction_count;
|
||||
} account;
|
||||
|
||||
// Global account array
|
||||
account accounts[NUM_ACCOUNTS];
|
||||
|
||||
// Teller threads
|
||||
|
||||
pthread_t tellers[NUM_TELLERS];
|
||||
|
||||
typedef struct teller_args {
|
||||
int teller_id;
|
||||
currency total;
|
||||
} teller_args;
|
||||
|
||||
void *teller_thread(void *arg) {
|
||||
teller_args *args = (teller_args *)arg;
|
||||
int id = args->teller_id;
|
||||
currency total = 0;
|
||||
|
||||
unsigned int seed = time(NULL) + pthread_self();
|
||||
for (int i = 0; i < TRANSACTIONS_PER_TELLER; i++) {
|
||||
printf("Thread %u: ", id);
|
||||
// Deposit or withdraw random currency amount from $0 to $1000
|
||||
currency random_amount = ((long)rand_r(&seed)) % 100000;
|
||||
if (rand_r(&seed) % 2) {
|
||||
printf("Withdrawing ");
|
||||
printf_currency(random_amount);
|
||||
random_amount = -random_amount;
|
||||
} else {
|
||||
printf("Depositing ");
|
||||
printf_currency(random_amount);
|
||||
}
|
||||
|
||||
int random_acc = rand_r(&seed) % NUM_ACCOUNTS;
|
||||
accounts[random_acc].balance += random_amount;
|
||||
accounts[random_acc].transaction_count++;
|
||||
|
||||
printf(" into account %u\n", random_acc);
|
||||
|
||||
total += random_amount;
|
||||
}
|
||||
|
||||
args->total = total;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int main() {
|
||||
// Initialize all accounts with 0 balance
|
||||
for (int i = 0; i < NUM_ACCOUNTS; i++) {
|
||||
accounts[i] = (account){.account_id = i, .balance = 0};
|
||||
}
|
||||
|
||||
// Initialize tellers
|
||||
teller_args args[NUM_TELLERS];
|
||||
for (int i = 0; i < NUM_TELLERS; i++) {
|
||||
args[i] = (teller_args){.teller_id = i};
|
||||
pthread_create(&tellers[i], NULL, teller_thread, &args[i]);
|
||||
}
|
||||
|
||||
// Wait for all threads to finish
|
||||
currency expected_total = 0;
|
||||
for (int i = 0; i < NUM_TELLERS; i++) {
|
||||
pthread_join(tellers[i], NULL);
|
||||
expected_total += args[i].total;
|
||||
}
|
||||
|
||||
currency actual_total = 0;
|
||||
for (int i = 0; i < NUM_ACCOUNTS; i++) {
|
||||
actual_total += accounts[i].balance;
|
||||
}
|
||||
|
||||
printf("Total balance: ");
|
||||
printf_currency(actual_total);
|
||||
printf("\nExpected total: ");
|
||||
printf_currency(expected_total);
|
||||
printf("\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
BIN
project1/phase2/output.png
Normal file
BIN
project1/phase2/output.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 326 KiB |
130
project1/phase2/phase2.c
Normal file
130
project1/phase2/phase2.c
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define NUM_ACCOUNTS 8
|
||||
#define NUM_TELLERS 128
|
||||
#define TRANSACTIONS_PER_TELLER 1000
|
||||
|
||||
// Monetary value in cents
|
||||
typedef long currency;
|
||||
|
||||
void sprintf_currency(char *buffer, currency c) {
|
||||
if (c < 0) {
|
||||
c = -c;
|
||||
sprintf(buffer, "-");
|
||||
buffer += sizeof(char);
|
||||
}
|
||||
sprintf(buffer, "$%ld.%.2ld", c / 100, c % 100);
|
||||
}
|
||||
|
||||
void printf_currency(currency c) {
|
||||
if (c < 0) {
|
||||
c = -c;
|
||||
printf("-");
|
||||
}
|
||||
printf("$%ld.%.2ld", c / 100, c % 100);
|
||||
}
|
||||
|
||||
// Accounts (shared resource)
|
||||
typedef struct account {
|
||||
int account_id;
|
||||
currency balance;
|
||||
int transaction_count;
|
||||
pthread_mutex_t lock; // Account access mutex
|
||||
} account;
|
||||
|
||||
void deposit(account *acc, currency amount) {
|
||||
pthread_mutex_lock(&acc->lock);
|
||||
// CRITICAL SECTION BEGIN
|
||||
acc->balance += amount;
|
||||
acc->transaction_count++;
|
||||
// CRITICAL SECTION END
|
||||
pthread_mutex_unlock(&acc->lock);
|
||||
}
|
||||
|
||||
// Global account array
|
||||
account accounts[NUM_ACCOUNTS];
|
||||
|
||||
// Teller threads
|
||||
|
||||
pthread_t tellers[NUM_TELLERS];
|
||||
|
||||
typedef struct teller_args {
|
||||
int teller_id;
|
||||
currency total;
|
||||
} teller_args;
|
||||
|
||||
void *teller_thread(void *arg) {
|
||||
teller_args *args = (teller_args *)arg;
|
||||
int id = args->teller_id;
|
||||
currency total = 0;
|
||||
|
||||
unsigned int seed = time(NULL) + pthread_self();
|
||||
for (int i = 0; i < TRANSACTIONS_PER_TELLER; i++) {
|
||||
// Deposit or withdraw random currency amount from $0 to $1000
|
||||
currency random_amount = ((long)rand_r(&seed)) % 100000;
|
||||
|
||||
char *operation;
|
||||
currency random_amount_i = random_amount;
|
||||
if (rand_r(&seed) % 2) {
|
||||
random_amount_i = -random_amount;
|
||||
operation = "Withdrawing";
|
||||
} else {
|
||||
operation = "Depositing";
|
||||
}
|
||||
|
||||
int random_acc = rand_r(&seed) % NUM_ACCOUNTS;
|
||||
deposit(&accounts[random_acc], random_amount_i);
|
||||
|
||||
// Display result of transaction
|
||||
char buffer[8];
|
||||
sprintf_currency(buffer, random_amount);
|
||||
printf("Thread %u: %s %s into account %u\n", id, operation, buffer,
|
||||
random_acc);
|
||||
|
||||
total += random_amount_i;
|
||||
}
|
||||
|
||||
args->total = total;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int main() {
|
||||
// Initialize all accounts with 0 balance
|
||||
for (int i = 0; i < NUM_ACCOUNTS; i++) {
|
||||
accounts[i] = (account){.account_id = i, .balance = 0};
|
||||
pthread_mutex_init(&accounts[i].lock, NULL);
|
||||
}
|
||||
|
||||
// Initialize tellers
|
||||
teller_args args[NUM_TELLERS];
|
||||
for (int i = 0; i < NUM_TELLERS; i++) {
|
||||
args[i] = (teller_args){.teller_id = i};
|
||||
pthread_create(&tellers[i], NULL, teller_thread, &args[i]);
|
||||
}
|
||||
|
||||
// Wait for all threads to finish
|
||||
currency expected_total = 0;
|
||||
for (int i = 0; i < NUM_TELLERS; i++) {
|
||||
pthread_join(tellers[i], NULL);
|
||||
expected_total += args[i].total;
|
||||
}
|
||||
|
||||
currency actual_total = 0;
|
||||
for (int i = 0; i < NUM_ACCOUNTS; i++) {
|
||||
actual_total += accounts[i].balance;
|
||||
}
|
||||
|
||||
printf("Total balance: ");
|
||||
printf_currency(actual_total);
|
||||
printf("\nExpected total: ");
|
||||
printf_currency(expected_total);
|
||||
printf("\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
BIN
project1/phase3/output.png
Normal file
BIN
project1/phase3/output.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 75 KiB |
105
project1/phase3/phase3.c
Normal file
105
project1/phase3/phase3.c
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define NUM_ACCOUNTS 5
|
||||
#define NUM_TRANSFERS 5
|
||||
|
||||
// Monetary value in cents
|
||||
typedef long currency;
|
||||
|
||||
void sprintf_currency(char *buffer, currency c) {
|
||||
if (c < 0) {
|
||||
c = -c;
|
||||
sprintf(buffer, "-");
|
||||
buffer += sizeof(char);
|
||||
}
|
||||
sprintf(buffer, "$%ld.%.2ld", c / 100, c % 100);
|
||||
}
|
||||
|
||||
void printf_currency(currency c) {
|
||||
if (c < 0) {
|
||||
c = -c;
|
||||
printf("-");
|
||||
}
|
||||
printf("$%ld.%.2ld", c / 100, c % 100);
|
||||
}
|
||||
|
||||
// Accounts (shared resource)
|
||||
typedef struct account {
|
||||
int account_id;
|
||||
currency balance;
|
||||
int transaction_count;
|
||||
pthread_mutex_t lock; // Account access mutex
|
||||
} account;
|
||||
|
||||
// Global account array
|
||||
account accounts[NUM_ACCOUNTS];
|
||||
|
||||
// Threads
|
||||
|
||||
pthread_t transfers[NUM_TRANSFERS];
|
||||
|
||||
typedef struct transfer_args {
|
||||
int transfer_id;
|
||||
account *from;
|
||||
account *to;
|
||||
currency amount;
|
||||
} transfer_args;
|
||||
|
||||
void *transfer_thread(void *arg) {
|
||||
transfer_args *args = (transfer_args *)arg;
|
||||
int id = args->transfer_id;
|
||||
account *from = args->from;
|
||||
account *to = args->to;
|
||||
currency amount = args->amount;
|
||||
|
||||
pthread_mutex_lock(&from->lock);
|
||||
printf("Thread %u: Locked account %d\n", id, from->account_id);
|
||||
|
||||
usleep(100); // Delay to account for other threads
|
||||
|
||||
pthread_mutex_lock(&to->lock);
|
||||
printf("Thread %u: Locked account %d\n", id, to->account_id);
|
||||
|
||||
from->balance -= amount;
|
||||
to->balance += amount;
|
||||
from->transaction_count++;
|
||||
to->transaction_count++;
|
||||
|
||||
printf("Thread %u: Unlocked account %u\n", id, from->account_id);
|
||||
printf("Thread %u: Unlocked account %u\n", id, to->account_id);
|
||||
pthread_mutex_unlock(&to->lock);
|
||||
pthread_mutex_unlock(&from->lock);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int main() {
|
||||
// Initialize all accounts with 0 balance
|
||||
for (int i = 0; i < NUM_ACCOUNTS; i++) {
|
||||
accounts[i] = (account){.account_id = i, .balance = 0};
|
||||
pthread_mutex_init(&accounts[i].lock, NULL);
|
||||
}
|
||||
|
||||
// Initialize transfers
|
||||
transfer_args args[NUM_TRANSFERS];
|
||||
for (int i = 0; i < NUM_TRANSFERS; i++) {
|
||||
args[i] = (transfer_args){.transfer_id = i,
|
||||
.from = &accounts[i % NUM_ACCOUNTS],
|
||||
.to = &accounts[(i + 1) % NUM_ACCOUNTS],
|
||||
.amount = 1000};
|
||||
pthread_create(&transfers[i], NULL, transfer_thread, &args[i]);
|
||||
}
|
||||
|
||||
// Wait for all threads to finish
|
||||
for (int i = 0; i < NUM_TRANSFERS; i++) {
|
||||
pthread_join(transfers[i], NULL);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
BIN
project1/phase4/mistake.png
Normal file
BIN
project1/phase4/mistake.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
BIN
project1/phase4/output.png
Normal file
BIN
project1/phase4/output.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 180 KiB |
BIN
project1/phase4/output_wrong.png
Normal file
BIN
project1/phase4/output_wrong.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 115 KiB |
124
project1/phase4/phase4.c
Normal file
124
project1/phase4/phase4.c
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
#include "bits/time.h"
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define NUM_ACCOUNTS 5
|
||||
#define NUM_TRANSFERS 20
|
||||
|
||||
// Monetary value in cents
|
||||
typedef long currency;
|
||||
|
||||
void sprintf_currency(char *buffer, currency c) {
|
||||
if (c < 0) {
|
||||
c = -c;
|
||||
sprintf(buffer, "-");
|
||||
buffer += sizeof(char);
|
||||
}
|
||||
sprintf(buffer, "$%ld.%.2ld", c / 100, c % 100);
|
||||
}
|
||||
|
||||
void printf_currency(currency c) {
|
||||
if (c < 0) {
|
||||
c = -c;
|
||||
printf("-");
|
||||
}
|
||||
printf("$%ld.%.2ld", c / 100, c % 100);
|
||||
}
|
||||
|
||||
// Accounts (shared resource)
|
||||
typedef struct account {
|
||||
int account_id;
|
||||
currency balance;
|
||||
int transaction_count;
|
||||
pthread_mutex_t lock; // Account access mutex
|
||||
} account;
|
||||
|
||||
// Global account array
|
||||
account accounts[NUM_ACCOUNTS];
|
||||
|
||||
// Threads
|
||||
|
||||
pthread_t transfers[NUM_TRANSFERS];
|
||||
|
||||
typedef struct transfer_args {
|
||||
int transfer_id;
|
||||
account *from;
|
||||
account *to;
|
||||
currency amount;
|
||||
} transfer_args;
|
||||
|
||||
void *transfer_thread(void *arg) {
|
||||
transfer_args *args = (transfer_args *)arg;
|
||||
int id = args->transfer_id;
|
||||
account *from = args->from;
|
||||
account *to = args->to;
|
||||
currency amount = args->amount;
|
||||
|
||||
// Sort in increasing order
|
||||
account *acc1;
|
||||
account *acc2;
|
||||
if (from->account_id < to->account_id) {
|
||||
acc1 = from;
|
||||
acc2 = to;
|
||||
} else {
|
||||
acc1 = to;
|
||||
acc2 = from;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&acc1->lock);
|
||||
printf("Thread %u: Locked account %u (1)\n", id, acc1->account_id);
|
||||
|
||||
usleep(100); // Delay to account for other threads
|
||||
|
||||
pthread_mutex_lock(&acc2->lock);
|
||||
printf("Thread %u: Locked account %u (2)\n", id, acc2->account_id);
|
||||
|
||||
from->balance -= amount;
|
||||
to->balance += amount;
|
||||
from->transaction_count++;
|
||||
to->transaction_count++;
|
||||
|
||||
printf("Thread %u: Unlocked account %u\n", id, acc1->account_id);
|
||||
printf("Thread %u: Unlocked account %u\n", id, acc2->account_id);
|
||||
pthread_mutex_unlock(&acc2->lock);
|
||||
pthread_mutex_unlock(&acc1->lock);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int main() {
|
||||
// Initialize all accounts with 0 balance
|
||||
for (int i = 0; i < NUM_ACCOUNTS; i++) {
|
||||
accounts[i] = (account){.account_id = i, .balance = 0};
|
||||
pthread_mutex_init(&accounts[i].lock, NULL);
|
||||
}
|
||||
|
||||
// Initialize tellers
|
||||
transfer_args args[NUM_TRANSFERS];
|
||||
for (int i = 0; i < NUM_TRANSFERS; i++) {
|
||||
args[i] = (transfer_args){.transfer_id = i,
|
||||
.from = &accounts[i % NUM_ACCOUNTS],
|
||||
.to = &accounts[(i + 1) % NUM_ACCOUNTS],
|
||||
.amount = 1000};
|
||||
pthread_create(&transfers[i], NULL, transfer_thread, &args[i]);
|
||||
}
|
||||
|
||||
// Wait for all threads to finish
|
||||
for (int i = 0; i < NUM_TRANSFERS; i++) {
|
||||
pthread_join(transfers[i], NULL);
|
||||
}
|
||||
|
||||
// Print account balance totals
|
||||
for (int i = 0; i < NUM_ACCOUNTS; i++) {
|
||||
printf("Total in account %u: ", i);
|
||||
printf_currency(accounts[i].balance);
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
177
project1/report/report.tex
Normal file
177
project1/report/report.tex
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
\documentclass{article}
|
||||
|
||||
\usepackage{graphicx}
|
||||
\usepackage{hyperref}
|
||||
|
||||
\title{CS3502 Project 1: Resource Management and Deadlocks}
|
||||
\author{Kiana Sheibani}
|
||||
|
||||
\begin{document}
|
||||
\maketitle
|
||||
\newpage
|
||||
|
||||
\section{Introduction}
|
||||
|
||||
To demonstrate the basic principles of multi-threaded code, race conditions, and deadlock
|
||||
avoidance, I ultimately decided to go with the model of a banking system, as that serves
|
||||
as a simple and immediately applicable demonstration of the concept.
|
||||
|
||||
The code for this project is stored in the \texttt{project1} subdirectory of my repository
|
||||
for this class: \url{https://github.com/tokinanpa/CS3502}.
|
||||
|
||||
\section{Phase 1: Basic Thread Operations}
|
||||
|
||||
\subsection{Implementation}
|
||||
|
||||
To start with, I simply needed some basic code of multiple threads trying to access and
|
||||
manipulate an array of bank accounts. This code mostly looked similar to the example code
|
||||
given in the instructions, except for the following differences:
|
||||
|
||||
\begin{enumerate}
|
||||
\item The currency amounts are stored as an integer number of cents instead of
|
||||
floating point values, because that's the sensible thing to do.
|
||||
\item Each thread is passed an argument struct that contains its own ID and a slot
|
||||
for it to pass back the total amount it deposited or withdrew.
|
||||
\end{enumerate}
|
||||
|
||||
The program initializes an array of accounts (8) and of teller threads (128), then each
|
||||
thread starts depositing or withdrawing a random amount of money up to \$1000 a certain
|
||||
amount of times (100), while keeping a count of the total monetary influx that thread
|
||||
introduced. After all threads have joined back up with the main program, the combined sum
|
||||
from each thread (the \emph{expected total}) is compared with the sum of all account
|
||||
balances (the \emph{actual total}). If no race conditions have occurred, these values
|
||||
should be equal.
|
||||
|
||||
\subsection{Results}
|
||||
|
||||
\begin{figure}[h!]
|
||||
\centering
|
||||
\includegraphics[width=0.5\linewidth]{../phase1/output.png}
|
||||
\caption{The output log of the Phase 1 program.}
|
||||
\label{fig:result1}
|
||||
\end{figure}
|
||||
|
||||
As expected, the program did indeed demonstrate that race conditions had occurred between
|
||||
the threads, as can be seen in \figurename~\ref{fig:result1}. You may notice that the
|
||||
transaction logs are somewhat garbled; this is because the logs are generated through
|
||||
multiple \texttt{printf} calls, resulting in another example of a race condition. I did
|
||||
not yet fix this bug because the repeated I/O calls helped delay the threads' execution
|
||||
and make race conditions more likely.
|
||||
|
||||
\section{Phase 2: Resource Protection}
|
||||
|
||||
\subsection{Implementation}
|
||||
|
||||
This was a very small addition to the code for Phase 1, and just required adding a mutex
|
||||
field \texttt{lock} to the account struct and initializing it at the beginning of the main
|
||||
program. Each thread had to lock the mutex before the transaction and unlock it after,
|
||||
which could be easily encapsulated into a subroutine \texttt{deposit} that takes a pointer
|
||||
to the account and an amount.
|
||||
|
||||
I also fixed the bug shown earlier that garbled the log output by combining the log into
|
||||
one \texttt{printf} call, which required a bit of rearranging in my code for printing currency
|
||||
values but was only a minor complication.
|
||||
|
||||
\subsection{Results}
|
||||
|
||||
\begin{figure}[h!]
|
||||
\centering
|
||||
\includegraphics[width=0.5\linewidth]{../phase2/output.png}
|
||||
\caption{The output of Phase 2, showing total consistency.}
|
||||
\label{fig:result2}
|
||||
\end{figure}
|
||||
|
||||
Even after increasing the number of transactions per teller from 100 to 1,000, the totals
|
||||
always match up exactly.
|
||||
|
||||
Notably, there seems to be large blocks of transactions coming from singular threads. This
|
||||
happened in the first program as well, most likely as an artifact of scheduling, but the
|
||||
blocks are much larger here. This may be due to threads repeatedly unlocking and
|
||||
re-locking a mutex, thus allowing them to instantly make another transaction.
|
||||
|
||||
\section{Phase 3: Deadlock Creation}
|
||||
|
||||
\subsection{Implementation}
|
||||
|
||||
It turns out that it's quite easy to make a program deadlock! I could have just had two
|
||||
transactions competing for resources as in the example code, but I decided to implement a
|
||||
more complex example. I kept the large array of threads, but changed the threads from
|
||||
adding/subtracting random amounts to transferring money between two accounts. The accounts
|
||||
to transfer between were passed as pointers to the threads using the struct-passing method
|
||||
I outlined earlier, along with the amount to transfer (I arbitrarily picked \$10 for all threads).
|
||||
|
||||
I then initialized the same number of threads as accounts, and set each of them to start a
|
||||
transfer from one account to the next. Thread 0 transfers from 0 to 1, thread 1 transfers
|
||||
from 1 to 2, etc. This would in theory create a large circle of threads that would
|
||||
deadlock and prevent any transaction from completing.
|
||||
|
||||
\subsection{Results}
|
||||
|
||||
\begin{figure}[h!]
|
||||
\centering
|
||||
\includegraphics[width=0.5\linewidth]{../phase3/output.png}
|
||||
\caption{The output of Phase 3 successfully generating a deadlock.}
|
||||
\label{fig:result3}
|
||||
\end{figure}
|
||||
|
||||
The program hangs immediately once all threads have started, regardless of the number of
|
||||
accounts or transfers I generate.
|
||||
|
||||
\section{Phase 4: Deadlock Resolution}
|
||||
|
||||
\subsection{Implementation}
|
||||
|
||||
To resolve the issue of these deadlocks in this code, I ultimately decided to go with a
|
||||
lock ordering approach. This is the simplest and easiest to implement of the possible
|
||||
solutions, though no program involving asynchonous execution is truly simple. The idea is
|
||||
to always lock the resources in a consistent, absolute order (in this case, the order of
|
||||
the account IDs). This would prevent the threads from creating a cycle of lock
|
||||
acquisitions, and thereby prevent any deadlocks from occurring.
|
||||
|
||||
\subsubsection{Challenges}
|
||||
|
||||
\begin{figure}[h!]
|
||||
\centering
|
||||
\includegraphics[width=0.5\linewidth]{../phase4/output_wrong.png}
|
||||
\caption{This log output shows account 0 being locked multiple times!}
|
||||
\label{fig:outputwrong}
|
||||
\end{figure}
|
||||
|
||||
After running the code with different parameters, in particular in the case of there being
|
||||
more transfers than accounts, I ran into a strange bug. It seemed like the mutexes were
|
||||
being locked multiple times at once, which should be impossible. The final balances of
|
||||
each account seemed to confirm that race conditions were occurring, as the total did not
|
||||
sum to 0, as should be the case when no transfer is adding or removing money from the
|
||||
bank.
|
||||
|
||||
\begin{figure}[h!]
|
||||
\centering
|
||||
\includegraphics[width=0.5\linewidth]{../phase4/mistake.png}
|
||||
\caption{The offending line of code (line 105).}
|
||||
\label{fig:mistake}
|
||||
\end{figure}
|
||||
|
||||
It took me a long time to discover the true problem: transactions were receiving invalid
|
||||
account IDs because one specific line of code accidentally left off a modulo operator
|
||||
(\texttt{i \% NUM\_ACCOUNTS}) to constrain the accounts referenced to inside the array.
|
||||
This meant that ``accounts'' were modified that did not actually exist, thus violating the
|
||||
preservation of the total sum of balances. The log output issue was completely unrelated
|
||||
and seems to solely be an artifact of race conditions in I/O operations. After fixing the
|
||||
aforementioned line of code, the final account balances were fully consistent.
|
||||
|
||||
\subsection{Results}
|
||||
|
||||
\begin{figure}[h!]
|
||||
\centering
|
||||
\includegraphics[width=0.5\linewidth]{../phase4/output.png}
|
||||
\caption{The output log of Phase 4 showing consistent account balances.}
|
||||
\label{fig:result4}
|
||||
\end{figure}
|
||||
|
||||
\figurename~\ref{fig:result4} shows that deadlocks have been completely avoided and that
|
||||
no race conditions have occurred. This output was generated with 5 accounts and 20
|
||||
transfers, meaning that every account has multiple threads attempting to modify its
|
||||
balance without issue. The performance is also unchanged from previous phases, which may
|
||||
not be the case for the other deadlock resolution methods (e.g. timed locks).
|
||||
|
||||
\end{document}
|
||||
1
project1/test.nix
Normal file
1
project1/test.nix
Normal file
|
|
@ -0,0 +1 @@
|
|||
{}
|
||||
12
shell.nix
12
shell.nix
|
|
@ -1,4 +1,10 @@
|
|||
{ mkShell }:
|
||||
{ mkShell, llvmPackages_21 }:
|
||||
|
||||
# GCC is provided by default
|
||||
mkShell {}
|
||||
mkShell {
|
||||
packages = [
|
||||
# GCC is provided by default
|
||||
|
||||
# Provides clangd
|
||||
llvmPackages_21.clang-tools
|
||||
];
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue