Disabled external gits
This commit is contained in:
15
cs453-ca/CS453-2021-project/sync-examples/Makefile
Normal file
15
cs453-ca/CS453-2021-project/sync-examples/Makefile
Normal file
@@ -0,0 +1,15 @@
|
||||
LDFLAGS += -pthread
|
||||
|
||||
BIN=counter1 counter2 counter3 counter4 election1 election2 election3 election4 procon1 procon2 procon3 procon4
|
||||
|
||||
all: ${BIN}
|
||||
.PHONY: all
|
||||
|
||||
clean:
|
||||
rm *.o ${BIN}
|
||||
.PHONY: clean
|
||||
|
||||
counter2: lock.o
|
||||
election2: lock.o
|
||||
procon2: lock.o
|
||||
procon4: lock.o
|
73
cs453-ca/CS453-2021-project/sync-examples/README.md
Normal file
73
cs453-ca/CS453-2021-project/sync-examples/README.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# Simple synchronization examples.
|
||||
|
||||
Modifying a variable that is concurrently accessed by another thread is
|
||||
dangerous and will (most of the time) lead to Undefined Behaviors and Data
|
||||
Races.
|
||||
|
||||
Through simple examples, we'll see how to use synchronization primitives to
|
||||
build thread-safe programs using locks and atomic variables.
|
||||
|
||||
We build 3 examples:
|
||||
- A counter. Each thread increments a shared counter repeatedly RUNS times.
|
||||
We check that the counter = RUNS * THREADS at the end.
|
||||
- Leader election. In each run, one thread is elected as the leader. We check
|
||||
that exactly one thread considers itself as the leader in each run.
|
||||
- Producer-Consumer. One thread generates data in a circular buffer while the
|
||||
second tries to consume it. They have to be kept in sync.
|
||||
|
||||
These examples also show how to start threads and wait for their completion.
|
||||
YOU DON'T HAVE TO START THREADS YOURSELVES IN THE DUAL-VERSIONED STM ALGO.
|
||||
In the STM, threads are started by the users of your library (in which they
|
||||
repeatedly (1) start a transaction, (2) read/write/alloc/free STM memory and (3)
|
||||
commit the transaction.).
|
||||
|
||||
## Counter
|
||||
|
||||
### Bad approach #1
|
||||
We employ no synchronization primitive: Undefined Behavior (UB), bad bad.
|
||||
|
||||
### Good approach #1
|
||||
We take a big nice lock around the ++: correct.
|
||||
|
||||
### Bad approach #2
|
||||
We use an atomic variable but use 2 non atomic operations: not UB, but doesn't
|
||||
work.
|
||||
|
||||
### Good approach #2
|
||||
We use an atomic variable and an atomic operation (fetch and add): correct.
|
||||
|
||||
## Leader election
|
||||
|
||||
### Bad approch # 1
|
||||
We employ no synchronization primitive: Undefined Behavior (UB), bad bad.
|
||||
|
||||
### Good approach #1
|
||||
We take a big nice lock around the test and set: correct.
|
||||
|
||||
### Bad approach #2
|
||||
We use an atomic variable but use 2 non atomic operations: not UB, but doesn't
|
||||
work.
|
||||
|
||||
### Good approach #2
|
||||
We use an atomic variable and an atomic operation (compare and swap): correct.
|
||||
|
||||
## Producer-consumer
|
||||
|
||||
### Bad approach
|
||||
We employ no synchronization primitive: Undefined Behavior (UB), bad bad.
|
||||
|
||||
### Okayish approach #1
|
||||
We take a big nice lock when when writting/reading and checking the bounds:
|
||||
correct.
|
||||
|
||||
### Okayish approach #2
|
||||
We use atomic variables and fences: correct.
|
||||
|
||||
### Good approach
|
||||
We use a conditional variable. :)
|
||||
https://www.ibm.com/docs/en/i/7.1?topic=ssw_ibm_i_71/apis/users_78.htm
|
||||
Conditional variables are synchronization primitives provided by the kernel
|
||||
that let a thread sleep until it's woken up. Using a "cond var", a consummer
|
||||
that realizes that data has not been generated yet can go to sleep instead of
|
||||
busy waiting. It will then be woken up by the producer once the data is
|
||||
generated. :)
|
42
cs453-ca/CS453-2021-project/sync-examples/counter1.c
Normal file
42
cs453-ca/CS453-2021-project/sync-examples/counter1.c
Normal file
@@ -0,0 +1,42 @@
|
||||
#include <pthread.h>
|
||||
#include <inttypes.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define RUNS (4096 * 256)
|
||||
#define THREADS 4
|
||||
|
||||
static int counter = 0;
|
||||
|
||||
void* count(void* null) {
|
||||
for (int r = 0; r < RUNS; r++) {
|
||||
counter++;
|
||||
// Equivalent to:
|
||||
//
|
||||
// int local = counter;
|
||||
// counter = local + 1;
|
||||
//
|
||||
// This is not thread-safe as the increment is not atomic.
|
||||
// **Incorrect**
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
pthread_t handlers[THREADS];
|
||||
|
||||
for (intptr_t i = 0; i < THREADS; i++) {
|
||||
int res = pthread_create(&handlers[i], NULL, count, NULL);
|
||||
assert(!res);
|
||||
}
|
||||
|
||||
for (int i = 0; i < THREADS; i++) {
|
||||
int res = pthread_join(handlers[i], NULL);
|
||||
assert(!res);
|
||||
}
|
||||
|
||||
if (counter != RUNS * THREADS) {
|
||||
printf("Didn't count so well. :/, found %d\n", counter);
|
||||
} else {
|
||||
printf("Counted up to %d.\n", counter);
|
||||
}
|
||||
}
|
45
cs453-ca/CS453-2021-project/sync-examples/counter2.c
Normal file
45
cs453-ca/CS453-2021-project/sync-examples/counter2.c
Normal file
@@ -0,0 +1,45 @@
|
||||
#include <pthread.h>
|
||||
#include <inttypes.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "lock.h"
|
||||
|
||||
#define RUNS (4096 * 256)
|
||||
#define THREADS 4
|
||||
|
||||
static struct lock_t lock;
|
||||
|
||||
static int counter = 0;
|
||||
|
||||
void* count(void* null) {
|
||||
for (int r = 0; r < RUNS; r++) {
|
||||
lock_acquire(&lock);
|
||||
counter++;
|
||||
lock_release(&lock);
|
||||
// Still equivalent, but only one thread accesses counter at a time, so it's
|
||||
// thread safe, no concurrent access/data race. :)
|
||||
// **Correct**
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
lock_init(&lock);
|
||||
pthread_t handlers[THREADS];
|
||||
|
||||
for (intptr_t i = 0; i < THREADS; i++) {
|
||||
int res = pthread_create(&handlers[i], NULL, count, NULL);
|
||||
assert(!res);
|
||||
}
|
||||
|
||||
for (int i = 0; i < THREADS; i++) {
|
||||
int res = pthread_join(handlers[i], NULL);
|
||||
assert(!res);
|
||||
}
|
||||
|
||||
if (counter != RUNS * THREADS) {
|
||||
printf("Didn't count so well. :/, found %d\n", counter);
|
||||
} else {
|
||||
printf("Counted up to %d.\n", counter);
|
||||
}
|
||||
}
|
38
cs453-ca/CS453-2021-project/sync-examples/counter3.c
Normal file
38
cs453-ca/CS453-2021-project/sync-examples/counter3.c
Normal file
@@ -0,0 +1,38 @@
|
||||
#include <pthread.h>
|
||||
#include <inttypes.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdatomic.h>
|
||||
|
||||
#define RUNS (4096 * 256)
|
||||
#define THREADS 4
|
||||
|
||||
static atomic_int counter = 0;
|
||||
|
||||
void* count(void* null) {
|
||||
for (int r = 0; r < RUNS; r++) {
|
||||
int read_copy = counter;
|
||||
counter = read_copy + 1;
|
||||
// **Incorrect**, using atomic variables (~atomic registers is not enough).
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
pthread_t handlers[THREADS];
|
||||
|
||||
for (intptr_t i = 0; i < THREADS; i++) {
|
||||
int res = pthread_create(&handlers[i], NULL, count, NULL);
|
||||
assert(!res);
|
||||
}
|
||||
|
||||
for (int i = 0; i < THREADS; i++) {
|
||||
int res = pthread_join(handlers[i], NULL);
|
||||
assert(!res);
|
||||
}
|
||||
|
||||
if (counter != RUNS * THREADS) {
|
||||
printf("Didn't count so well. :/, found %d\n", counter);
|
||||
} else {
|
||||
printf("Counted up to %d.\n", counter);
|
||||
}
|
||||
}
|
37
cs453-ca/CS453-2021-project/sync-examples/counter4.c
Normal file
37
cs453-ca/CS453-2021-project/sync-examples/counter4.c
Normal file
@@ -0,0 +1,37 @@
|
||||
#include <pthread.h>
|
||||
#include <inttypes.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdatomic.h>
|
||||
|
||||
#define RUNS (4096 * 256)
|
||||
#define THREADS 4
|
||||
|
||||
static atomic_int counter = 0;
|
||||
|
||||
void* count(void* null) {
|
||||
for (int r = 0; r < RUNS; r++) {
|
||||
atomic_fetch_add(&counter, 1);
|
||||
// **Correct**, using an atomic operation (most likely) provided by the CPU.
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
pthread_t handlers[THREADS];
|
||||
|
||||
for (intptr_t i = 0; i < THREADS; i++) {
|
||||
int res = pthread_create(&handlers[i], NULL, count, NULL);
|
||||
assert(!res);
|
||||
}
|
||||
|
||||
for (int i = 0; i < THREADS; i++) {
|
||||
int res = pthread_join(handlers[i], NULL);
|
||||
assert(!res);
|
||||
}
|
||||
|
||||
if (counter != RUNS * THREADS) {
|
||||
printf("Didn't count so well. :/, found %d\n", counter);
|
||||
} else {
|
||||
printf("Counted up to %d.\n", counter);
|
||||
}
|
||||
}
|
48
cs453-ca/CS453-2021-project/sync-examples/election1.c
Normal file
48
cs453-ca/CS453-2021-project/sync-examples/election1.c
Normal file
@@ -0,0 +1,48 @@
|
||||
#include <pthread.h>
|
||||
#include <inttypes.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdatomic.h>
|
||||
|
||||
#define RUNS (4096 * 256)
|
||||
#define THREADS 4
|
||||
|
||||
static int leader[RUNS] = { 0 };
|
||||
static atomic_int nb_leaders[RUNS] = { 0 }; // used to check correctness
|
||||
|
||||
void* elect(void* _tid) {
|
||||
intptr_t tid = (intptr_t) _tid;
|
||||
// For each run (or round) we try to impose ourselves as the run's leader.
|
||||
for (int r = 0; r < RUNS; r++) {
|
||||
if (leader[r] == 0) {
|
||||
leader[r] = tid;
|
||||
atomic_fetch_add(&nb_leaders[r], 1); // used to check correctness
|
||||
}
|
||||
// **Incorrect**, super wrong, plz synchronize.
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
pthread_t handlers[THREADS];
|
||||
|
||||
for (intptr_t i = 0; i < THREADS; i++) {
|
||||
int res = pthread_create(&handlers[i], NULL, elect, (void*)(i + 1));
|
||||
assert(!res);
|
||||
}
|
||||
|
||||
for (int i = 0; i < THREADS; i++) {
|
||||
int res = pthread_join(handlers[i], NULL);
|
||||
assert(!res);
|
||||
}
|
||||
|
||||
int r = 0;
|
||||
for (; r < RUNS; r++) {
|
||||
if (nb_leaders[r] != 1) {
|
||||
printf("Leader election for round %d failed.\n", r);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (r == RUNS) {
|
||||
printf("Looks correct to me! :)\n");
|
||||
}
|
||||
}
|
54
cs453-ca/CS453-2021-project/sync-examples/election2.c
Normal file
54
cs453-ca/CS453-2021-project/sync-examples/election2.c
Normal file
@@ -0,0 +1,54 @@
|
||||
#include <pthread.h>
|
||||
#include <inttypes.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdatomic.h>
|
||||
|
||||
#include "lock.h"
|
||||
|
||||
#define RUNS (4096 * 256)
|
||||
#define THREADS 4
|
||||
|
||||
static struct lock_t lock;
|
||||
|
||||
static int leader[RUNS] = { 0 };
|
||||
static atomic_int nb_leaders[RUNS] = { 0 }; // used to check correctness
|
||||
|
||||
void* elect(void* _tid) {
|
||||
intptr_t tid = (intptr_t) _tid;
|
||||
for (int r = 0; r < RUNS; r++) {
|
||||
lock_acquire(&lock);
|
||||
if (leader[r] == 0) {
|
||||
leader[r] = tid;
|
||||
atomic_fetch_add(&nb_leaders[r], 1); // used to check correctness
|
||||
}
|
||||
lock_release(&lock);
|
||||
// **Correct**, a single thread tries to become leader at a time.
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
lock_init(&lock);
|
||||
pthread_t handlers[THREADS];
|
||||
|
||||
for (intptr_t i = 0; i < THREADS; i++) {
|
||||
int res = pthread_create(&handlers[i], NULL, elect, (void*)(i + 1));
|
||||
assert(!res);
|
||||
}
|
||||
|
||||
for (int i = 0; i < THREADS; i++) {
|
||||
int res = pthread_join(handlers[i], NULL);
|
||||
assert(!res);
|
||||
}
|
||||
|
||||
int r = 0;
|
||||
for (; r < RUNS; r++) {
|
||||
if (nb_leaders[r] != 1) {
|
||||
printf("Leader election for round %d failed.\n", r);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (r == RUNS) {
|
||||
printf("Looks correct to me! :)\n");
|
||||
}
|
||||
}
|
48
cs453-ca/CS453-2021-project/sync-examples/election3.c
Normal file
48
cs453-ca/CS453-2021-project/sync-examples/election3.c
Normal file
@@ -0,0 +1,48 @@
|
||||
#include <pthread.h>
|
||||
#include <inttypes.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdatomic.h>
|
||||
|
||||
#define RUNS (4096 * 256)
|
||||
#define THREADS 4
|
||||
|
||||
static atomic_int leader[RUNS] = { 0 };
|
||||
static atomic_int nb_leaders[RUNS] = { 0 }; // used to check correctness
|
||||
|
||||
void* elect(void* _tid) {
|
||||
intptr_t tid = (intptr_t) _tid;
|
||||
for (int r = 0; r < RUNS; r++) {
|
||||
if (leader[r] == 0) {
|
||||
leader[r] = tid;
|
||||
atomic_fetch_add(&nb_leaders[r], 1); // used to check correctness
|
||||
}
|
||||
// **Incorrect**, atomic variables (~atomic registers) do not enforce atomic
|
||||
// operation blocks.
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
pthread_t handlers[THREADS];
|
||||
|
||||
for (intptr_t i = 0; i < THREADS; i++) {
|
||||
int res = pthread_create(&handlers[i], NULL, elect, (void*)(i + 1));
|
||||
assert(!res);
|
||||
}
|
||||
|
||||
for (int i = 0; i < THREADS; i++) {
|
||||
int res = pthread_join(handlers[i], NULL);
|
||||
assert(!res);
|
||||
}
|
||||
|
||||
int r = 0;
|
||||
for (; r < RUNS; r++) {
|
||||
if (nb_leaders[r] != 1) {
|
||||
printf("Leader election for round %d failed.\n", r);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (r == RUNS) {
|
||||
printf("Looks correct to me! :)\n");
|
||||
}
|
||||
}
|
48
cs453-ca/CS453-2021-project/sync-examples/election4.c
Normal file
48
cs453-ca/CS453-2021-project/sync-examples/election4.c
Normal file
@@ -0,0 +1,48 @@
|
||||
#include <pthread.h>
|
||||
#include <inttypes.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdatomic.h>
|
||||
|
||||
#define RUNS (4096 * 256)
|
||||
#define THREADS 4
|
||||
|
||||
static atomic_int leader[RUNS] = { 0 };
|
||||
static atomic_int nb_leaders[RUNS] = { 0 }; // used to check correctness
|
||||
|
||||
void* elect(void* _tid) {
|
||||
intptr_t tid = (intptr_t) _tid;
|
||||
for (int r = 0; r < RUNS; r++) {
|
||||
int expected = 0;
|
||||
if (atomic_compare_exchange_strong(&leader[r], &expected, tid)) {
|
||||
atomic_fetch_add(&nb_leaders[r], 1); // used to check correctness
|
||||
}
|
||||
// **Correct**, we use the Compare and Swap operation to atomically check if
|
||||
// a leader has already been elected and if not, we impose ourselves. :)
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
pthread_t handlers[THREADS];
|
||||
|
||||
for (intptr_t i = 0; i < THREADS; i++) {
|
||||
int res = pthread_create(&handlers[i], NULL, elect, (void*)(i + 1));
|
||||
assert(!res);
|
||||
}
|
||||
|
||||
for (int i = 0; i < THREADS; i++) {
|
||||
int res = pthread_join(handlers[i], NULL);
|
||||
assert(!res);
|
||||
}
|
||||
|
||||
int r = 0;
|
||||
for (; r < RUNS; r++) {
|
||||
if (nb_leaders[r] != 1) {
|
||||
printf("Leader election for round %d failed.\n", r);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (r == RUNS) {
|
||||
printf("Looks correct to me! :)\n");
|
||||
}
|
||||
}
|
27
cs453-ca/CS453-2021-project/sync-examples/lock.c
Normal file
27
cs453-ca/CS453-2021-project/sync-examples/lock.c
Normal file
@@ -0,0 +1,27 @@
|
||||
#include "lock.h"
|
||||
|
||||
bool lock_init(struct lock_t* lock) {
|
||||
return pthread_mutex_init(&(lock->mutex), NULL) == 0
|
||||
&& pthread_cond_init(&(lock->cv), NULL) == 0;
|
||||
}
|
||||
|
||||
void lock_cleanup(struct lock_t* lock) {
|
||||
pthread_mutex_destroy(&(lock->mutex));
|
||||
pthread_cond_destroy(&(lock->cv));
|
||||
}
|
||||
|
||||
bool lock_acquire(struct lock_t* lock) {
|
||||
return pthread_mutex_lock(&(lock->mutex)) == 0;
|
||||
}
|
||||
|
||||
void lock_release(struct lock_t* lock) {
|
||||
pthread_mutex_unlock(&(lock->mutex));
|
||||
}
|
||||
|
||||
void lock_wait(struct lock_t* lock) {
|
||||
pthread_cond_wait(&(lock->cv), &(lock->mutex));
|
||||
}
|
||||
|
||||
void lock_wake_up(struct lock_t* lock) {
|
||||
pthread_cond_broadcast(&(lock->cv));
|
||||
}
|
47
cs453-ca/CS453-2021-project/sync-examples/lock.h
Normal file
47
cs453-ca/CS453-2021-project/sync-examples/lock.h
Normal file
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/**
|
||||
* @brief A lock that can only be taken exclusively. Contrarily to shared locks,
|
||||
* exclusive locks have wait/wake_up capabilities.
|
||||
*/
|
||||
struct lock_t {
|
||||
pthread_mutex_t mutex;
|
||||
pthread_cond_t cv;
|
||||
};
|
||||
|
||||
/** Initialize the given lock.
|
||||
* @param lock Lock to initialize
|
||||
* @return Whether the operation is a success
|
||||
**/
|
||||
bool lock_init(struct lock_t* lock);
|
||||
|
||||
/** Clean up the given lock.
|
||||
* @param lock Lock to clean up
|
||||
**/
|
||||
void lock_cleanup(struct lock_t* lock);
|
||||
|
||||
/** Wait and acquire the given lock.
|
||||
* @param lock Lock to acquire
|
||||
* @return Whether the operation is a success
|
||||
**/
|
||||
bool lock_acquire(struct lock_t* lock);
|
||||
|
||||
/** Release the given lock.
|
||||
* @param lock Lock to release
|
||||
**/
|
||||
void lock_release(struct lock_t* lock);
|
||||
|
||||
/** Wait until woken up by a signal on the given lock.
|
||||
* The lock is released until lock_wait completes at which point it is acquired
|
||||
* again. Exclusive lock access is enforced.
|
||||
* @param lock Lock to release (until woken up) and wait on.
|
||||
**/
|
||||
void lock_wait(struct lock_t* lock);
|
||||
|
||||
/** Wake up all threads waiting on the given lock.
|
||||
* @param lock Lock on which other threads are waiting.
|
||||
**/
|
||||
void lock_wake_up(struct lock_t* lock);
|
72
cs453-ca/CS453-2021-project/sync-examples/procon1.c
Normal file
72
cs453-ca/CS453-2021-project/sync-examples/procon1.c
Normal file
@@ -0,0 +1,72 @@
|
||||
#include <pthread.h>
|
||||
#include <inttypes.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define RUNS 4096
|
||||
#define THREADS 4
|
||||
#define DATA_TEXT_SIZE 1024
|
||||
#define BUFFER_SIZE 1024
|
||||
|
||||
struct data {
|
||||
char text[DATA_TEXT_SIZE];
|
||||
};
|
||||
|
||||
bool are_same(struct data* a, struct data* b) {
|
||||
for (int i = 0; i < DATA_TEXT_SIZE; i++)
|
||||
if (a->text[i] != b->text[i]) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
struct data buffer[BUFFER_SIZE] = { 0 };
|
||||
|
||||
struct data produced[RUNS] = { 0 }; // used to check correctness
|
||||
struct data consumed[RUNS] = { 0 }; // used to check correctness
|
||||
|
||||
void* produce(void* null) {
|
||||
for (int r = 0; r < RUNS; r++) {
|
||||
for (int i = 0; i < DATA_TEXT_SIZE; i++)
|
||||
produced[r].text[i] = rand();
|
||||
buffer[r % BUFFER_SIZE] = produced[r];
|
||||
// Super wrong: Overwritting data being read by the consumer.
|
||||
}
|
||||
}
|
||||
|
||||
void* consume(void* null) {
|
||||
for (int r = 0; r < RUNS; r++) {
|
||||
consumed[r] = buffer[r % BUFFER_SIZE];
|
||||
// Super wrong: Reading data concurrently (or not even) written by the
|
||||
// producer.
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
int res;
|
||||
pthread_t producer;
|
||||
res = pthread_create(&producer, NULL, produce, NULL);
|
||||
assert(!res);
|
||||
|
||||
pthread_t consumer;
|
||||
res = pthread_create(&consumer, NULL, consume, NULL);
|
||||
assert(!res);
|
||||
|
||||
res = pthread_join(consumer, NULL);
|
||||
assert(!res);
|
||||
|
||||
res = pthread_join(producer, NULL);
|
||||
assert(!res);
|
||||
|
||||
int r = 0;
|
||||
for (; r < RUNS; r++) {
|
||||
if (!are_same(&produced[r], &consumed[r])) {
|
||||
printf("Consumed the wrong data on round %d.\n", r);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (r == RUNS) {
|
||||
printf("Looks correct to me! :)\n");
|
||||
}
|
||||
}
|
97
cs453-ca/CS453-2021-project/sync-examples/procon2.c
Normal file
97
cs453-ca/CS453-2021-project/sync-examples/procon2.c
Normal file
@@ -0,0 +1,97 @@
|
||||
#include <pthread.h>
|
||||
#include <inttypes.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "lock.h"
|
||||
|
||||
#define RUNS 4096
|
||||
#define THREADS 4
|
||||
#define DATA_TEXT_SIZE 1024
|
||||
#define BUFFER_SIZE 1024
|
||||
|
||||
struct data {
|
||||
char text[DATA_TEXT_SIZE];
|
||||
};
|
||||
|
||||
static struct lock_t lock;
|
||||
|
||||
bool are_same(struct data* a, struct data* b) {
|
||||
for (int i = 0; i < DATA_TEXT_SIZE; i++)
|
||||
if (a->text[i] != b->text[i]) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
struct data buffer[BUFFER_SIZE] = { 0 };
|
||||
|
||||
int produced_until = 0;
|
||||
int consumed_until = 0;
|
||||
|
||||
struct data produced[RUNS] = { 0 }; // used to check correctness
|
||||
struct data consumed[RUNS] = { 0 }; // used to check correctness
|
||||
|
||||
void* produce(void* null) {
|
||||
for (int r = 0; r < RUNS; r++) {
|
||||
while (true) {
|
||||
lock_acquire(&lock);
|
||||
if (consumed_until + BUFFER_SIZE > r) break;
|
||||
lock_release(&lock);
|
||||
}
|
||||
printf("can produce %d\n", r);
|
||||
for (int i = 0; i < DATA_TEXT_SIZE; i++)
|
||||
produced[r].text[i] = rand();
|
||||
buffer[r % BUFFER_SIZE] = produced[r];
|
||||
produced_until++;
|
||||
lock_release(&lock);
|
||||
// Correct: we wait until nobody consumed data anymore and correctly release
|
||||
// (prevent reordering) thanks to the lock.
|
||||
}
|
||||
}
|
||||
|
||||
void* consume(void* null) {
|
||||
for (int r = 0; r < RUNS; r++) {
|
||||
while (true) {
|
||||
lock_acquire(&lock);
|
||||
if (produced_until > r) break;
|
||||
lock_release(&lock);
|
||||
}
|
||||
printf("can consume %d\n", r);
|
||||
consumed[r] = buffer[r % BUFFER_SIZE];
|
||||
consumed_until++;
|
||||
lock_release(&lock);
|
||||
// Correct: we wait until nobody produces data anymore and correctly acquire
|
||||
// + release (prevent reordering) thanks to the lock.
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
lock_init(&lock);
|
||||
int res;
|
||||
pthread_t producer;
|
||||
res = pthread_create(&producer, NULL, produce, NULL);
|
||||
assert(!res);
|
||||
|
||||
pthread_t consumer;
|
||||
res = pthread_create(&consumer, NULL, consume, NULL);
|
||||
assert(!res);
|
||||
|
||||
res = pthread_join(consumer, NULL);
|
||||
assert(!res);
|
||||
|
||||
res = pthread_join(producer, NULL);
|
||||
assert(!res);
|
||||
|
||||
int r = 0;
|
||||
for (; r < RUNS; r++) {
|
||||
if (!are_same(&produced[r], &consumed[r])) {
|
||||
printf("Consumed the wrong data on round %d.\n", r);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (r == RUNS) {
|
||||
printf("Looks correct to me! :)\n");
|
||||
}
|
||||
}
|
91
cs453-ca/CS453-2021-project/sync-examples/procon3.c
Normal file
91
cs453-ca/CS453-2021-project/sync-examples/procon3.c
Normal file
@@ -0,0 +1,91 @@
|
||||
#include <pthread.h>
|
||||
#include <inttypes.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define RUNS 4096
|
||||
#define THREADS 4
|
||||
#define DATA_TEXT_SIZE 1024
|
||||
#define BUFFER_SIZE 1024
|
||||
|
||||
struct data {
|
||||
char text[DATA_TEXT_SIZE];
|
||||
};
|
||||
|
||||
bool are_same(struct data* a, struct data* b) {
|
||||
for (int i = 0; i < DATA_TEXT_SIZE; i++)
|
||||
if (a->text[i] != b->text[i]) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
struct data buffer[BUFFER_SIZE] = { 0 };
|
||||
|
||||
atomic_int produced_until = 0;
|
||||
atomic_int consumed_until = 0;
|
||||
|
||||
struct data produced[RUNS] = { 0 }; // used to check correctness
|
||||
struct data consumed[RUNS] = { 0 }; // used to check correctness
|
||||
|
||||
void* produce(void* null) {
|
||||
for (int r = 0; r < RUNS; r++) {
|
||||
while (true) {
|
||||
// The memory order can be relaxed as we don't read anything "produced"
|
||||
// by the consumer.
|
||||
int local_cu = atomic_load_explicit(&consumed_until, memory_order_relaxed);
|
||||
if (local_cu + BUFFER_SIZE > r) break;
|
||||
}
|
||||
printf("can produce %d\n", r);
|
||||
for (int i = 0; i < DATA_TEXT_SIZE; i++)
|
||||
produced[r].text[i] = rand();
|
||||
buffer[r % BUFFER_SIZE] = produced[r];
|
||||
// We want to increment "produced_until" after the buffer has been written.
|
||||
// By using memory_order_release, we prevent the STOREs on buffer from being
|
||||
// reordered after the atomic operation.
|
||||
atomic_fetch_add_explicit(&produced_until, 1, memory_order_release);
|
||||
}
|
||||
}
|
||||
|
||||
void* consume(void* null) {
|
||||
for (int r = 0; r < RUNS; r++) {
|
||||
while (true) {
|
||||
// We don't want to access the buffer before checking the atomic variable.
|
||||
// The memory_order_acquire prevents this reordering.
|
||||
int local_pu = atomic_load_explicit(&produced_until, memory_order_acquire);
|
||||
if (local_pu > r) break;
|
||||
}
|
||||
printf("can consume %d\n", r);
|
||||
consumed[r] = buffer[r % BUFFER_SIZE];
|
||||
atomic_fetch_add_explicit(&consumed_until, 1, memory_order_release);
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
int res;
|
||||
pthread_t producer;
|
||||
res = pthread_create(&producer, NULL, produce, NULL);
|
||||
assert(!res);
|
||||
|
||||
pthread_t consumer;
|
||||
res = pthread_create(&consumer, NULL, consume, NULL);
|
||||
assert(!res);
|
||||
|
||||
res = pthread_join(consumer, NULL);
|
||||
assert(!res);
|
||||
|
||||
res = pthread_join(producer, NULL);
|
||||
assert(!res);
|
||||
|
||||
int r = 0;
|
||||
for (; r < RUNS; r++) {
|
||||
if (!are_same(&produced[r], &consumed[r])) {
|
||||
printf("Consumed the wrong data on round %d.\n", r);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (r == RUNS) {
|
||||
printf("Looks correct to me! :)\n");
|
||||
}
|
||||
}
|
103
cs453-ca/CS453-2021-project/sync-examples/procon4.c
Normal file
103
cs453-ca/CS453-2021-project/sync-examples/procon4.c
Normal file
@@ -0,0 +1,103 @@
|
||||
#include <pthread.h>
|
||||
#include <inttypes.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "lock.h"
|
||||
|
||||
#define RUNS 4096
|
||||
#define THREADS 4
|
||||
#define DATA_TEXT_SIZE 1024
|
||||
#define BUFFER_SIZE 8
|
||||
|
||||
struct data {
|
||||
char text[DATA_TEXT_SIZE];
|
||||
};
|
||||
|
||||
static struct lock_t lock;
|
||||
|
||||
bool are_same(struct data* a, struct data* b) {
|
||||
for (int i = 0; i < DATA_TEXT_SIZE; i++)
|
||||
if (a->text[i] != b->text[i]) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
struct data buffer[BUFFER_SIZE] = { 0 };
|
||||
|
||||
int produced_until = 0;
|
||||
int consumed_until = 0;
|
||||
|
||||
struct data produced[RUNS] = { 0 }; // used to check correctness
|
||||
struct data consumed[RUNS] = { 0 }; // used to check correctness
|
||||
|
||||
void* produce(void* null) {
|
||||
for (int r = 0; r < RUNS; r++) {
|
||||
lock_acquire(&lock);
|
||||
while (true) {
|
||||
if (consumed_until + BUFFER_SIZE > r) break;
|
||||
lock_wait(&lock);
|
||||
// Note: waiting releases the lock and puts the thread to sleep. Once
|
||||
// woken up, it will need to acquire back the lock to continue its
|
||||
// execution.
|
||||
// Said differently, if multiple threads are waiting, upon "lock_wake_up",
|
||||
// only one of them will continue the execution at a time (because the
|
||||
// lock is exclusive).
|
||||
}
|
||||
printf("can produce %d\n", r);
|
||||
for (int i = 0; i < DATA_TEXT_SIZE; i++)
|
||||
produced[r].text[i] = rand();
|
||||
buffer[r % BUFFER_SIZE] = produced[r];
|
||||
produced_until++;
|
||||
lock_release(&lock);
|
||||
lock_wake_up(&lock); // We tell the consumer it can continue consuming.
|
||||
// Correct: This is a better version of the lock one in which we do not busy
|
||||
// wait but rely on a notification primitive to let the "idle" cores rest.
|
||||
}
|
||||
}
|
||||
|
||||
void* consume(void* null) {
|
||||
for (int r = 0; r < RUNS; r++) {
|
||||
lock_acquire(&lock);
|
||||
while (true) {
|
||||
if (produced_until > r) break;
|
||||
lock_wait(&lock);
|
||||
}
|
||||
printf("can consume %d\n", r);
|
||||
consumed[r] = buffer[r % BUFFER_SIZE];
|
||||
consumed_until++;
|
||||
lock_release(&lock);
|
||||
lock_wake_up(&lock); // We tell the producer it can continue producing.
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
lock_init(&lock);
|
||||
int res;
|
||||
pthread_t producer;
|
||||
res = pthread_create(&producer, NULL, produce, NULL);
|
||||
assert(!res);
|
||||
|
||||
pthread_t consumer;
|
||||
res = pthread_create(&consumer, NULL, consume, NULL);
|
||||
assert(!res);
|
||||
|
||||
res = pthread_join(consumer, NULL);
|
||||
assert(!res);
|
||||
|
||||
res = pthread_join(producer, NULL);
|
||||
assert(!res);
|
||||
|
||||
int r = 0;
|
||||
for (; r < RUNS; r++) {
|
||||
if (!are_same(&produced[r], &consumed[r])) {
|
||||
printf("Consumed the wrong data on round %d.\n", r);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (r == RUNS) {
|
||||
printf("Looks correct to me! :)\n");
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user