Skip to content
Snippets Groups Projects
Commit fe36e0a8 authored by Mactavish's avatar Mactavish
Browse files

init tutorial-4

parent 6605b30a
Branches
No related tags found
No related merge requests found
# ALP4 Tutorial-3
# ALP4 Tutorial-4
This branch contains all materials for the 3rd tutorial session.
This branch contains all materials for the 4th tutorial session.
## Agenda
- Assignment's solution presentation (if any)
- Recap & Discussion: Concurrency, Threads
- Pthreads API
- Make & Makefile
- Recap & Discussion: Petri Nets, Performance, More on Threads
- Q&A
PROG = main
OBJECTS = main.o counter.o
CC = gcc
CFLAGS = -Wall -std=c99 -pthread
CFLAGS += -I. # add the current directory to the include path
.Phone: all
all: clean $(PROG) # build and run the program
./$(PROG)
$(PROG): $(OBJECTS) # link the object files into a binary
$(CC) $(CFLAGS) $^ -o $@
$(OBJECTS): %.o: %.c counter.h # compile the source files into object files
$(CC) $(CFLAGS) -c $<
.PHONY: clean
clean: # remove the object files and the binary
rm -f $(OBJECTS) $(PROG)
#include "counter.h"
void init_counter(counter_t *c, int threshold, int num_cpu) {
c->num_cpu = num_cpu;
c->threshold = threshold;
c->global = 0;
c->local = malloc(num_cpu * sizeof(long));
c->llock = malloc(num_cpu * sizeof(pthread_mutex_t));
pthread_mutex_init(&c->glock, NULL);
int i;
for (i = 0; i < num_cpu; i++) {
c->local[i] = 0;
pthread_mutex_init(&c->llock[i], NULL);
}
}
void update_counter(counter_t * c, int t_id, int amount) {
int cpu = t_id % c->num_cpu;
pthread_mutex_lock(&c->llock[cpu]);
c->local[cpu] += amount;
if (c->local[cpu] >= c->threshold) {
pthread_mutex_lock(&c->glock);
c->global += c->local[cpu];
pthread_mutex_unlock(&c->glock);
c->local[cpu] = 0;
}
pthread_mutex_unlock(&c->llock[cpu]);
}
int get_counter_val(counter_t *c) {
pthread_mutex_lock(&c->glock);
int val = c->global;
pthread_mutex_unlock(&c->glock);
return val;
}
void sync_counter(counter_t *c) {
int i;
for (i = 0; i < c->num_cpu; i++) {
pthread_mutex_lock(&c->llock[i]);
pthread_mutex_lock(&c->glock);
c->global += c->local[i];
pthread_mutex_unlock(&c->glock);
c->local[i] = 0;
pthread_mutex_unlock(&c->llock[i]);
}
}
void destroy_counter(counter_t *c) {
pthread_mutex_destroy(&c->glock);
int i;
for (i = 0; i < c->num_cpu; i++) {
pthread_mutex_destroy(&c->llock[i]);
}
free(c->local);
free(c->llock);
}
#include <pthread.h>
#include <stdlib.h>
typedef struct __counter_t {
int num_cpu; // number of cpus
long long global; // global count
pthread_mutex_t glock; // global lock
long *local; // local count (per cpu)
pthread_mutex_t *llock; // ... and locks
int threshold; // update frequency
} counter_t;
void init_counter(counter_t *c, int threshold, int num_cpu);
void update_counter(counter_t * c, int t_id, int amount);
int get_counter_val(counter_t *c);
void sync_counter(counter_t *c);
void destroy_counter(counter_t *c);
#include "counter.h"
#include <stdio.h>
#include <unistd.h>
#define COUNT_LIMIT 10000000
#define THREADSHOLD 1024
counter_t counter;
void *worker(void *t_id) {
printf("Thread %d started\n", (int) t_id);
for (int i = 0; i < COUNT_LIMIT; i++) {
update_counter(&counter, (int) t_id, 1);
}
}
int main() {
long num_cpus = sysconf(_SC_NPROCESSORS_ONLN);
printf("Number of CPUs: %ld\n", num_cpus);
pthread_t *threads = malloc(num_cpus * sizeof(pthread_t));
init_counter(&counter, THREADSHOLD, num_cpus);
for (int i = 0; i < num_cpus; i++) {
pthread_create(&threads[i], NULL, worker, (void *) i);
}
for (int i = 0; i < num_cpus; i++) {
pthread_join(threads[i], NULL);
}
sync_counter(&counter);
printf("Counter value: %d\n", get_counter_val(&counter));
destroy_counter(&counter);
return 0;
}
slides/images/mutex.png

44.9 KiB

slides/images/thread-memory-model.png

80.7 KiB

---
title: Make
---
# Make: a build automation tool
### Intuition
- Yes, I can just type `gcc ...` to compile my tiny program.
- What about when you have tens, hundreds or even thousands of files?
- Even worse, these files can even be spread over different folders.
- We need a program to manage the compiling of all the files in our programs.
- **Make** is such a tool that can automate the build process.
<br/>
### What is Build Automation?
- Automating the process of compiling code into libraries and executables
- Recompile only the files that have changed
- There are many tools for this: [Apache Maven](https://maven.apache.org/), [Gradle](https://gradle.org/), [CMake](https://cmake.org/)
---
title: Make II
---
### What is Make?
- Make uses a **declarative language** as opposed to procedural languages.
- We tell Make what we want.
- We provide a set of rules showing dependencies between files.
- Make uses the rules to get the job done.
- Make is associated with the executable named `make`.
- There are different variants of Make, we focus on the [GNU Make](https://www.gnu.org/software/make/)
<br/>
### Makefile
Make uses a file called `Makefile` (or `makefile`).
We recommend `Makefile` because it appears prominently near the beginning of a directory listing, right near other important files such as README.
---
title: Makefile Overview
---
Here is an example of a Makefile:
```make
# Recursive variables
PROG = main
OBJS = main.o
CFLAG = -Wall -g
CC = gcc
INCLUDE = -I.
# Simple rules
$(PROG): $(OBJS)
$(CC) $(CFLAGS) $(INCLUDES) -o $@ $(OBJS) # Commands
# Pattern rules
%.o: %.c
$(CC) -c -o $@ $< $(CFLAGS) $(INCLUDES)
# Phony targets
.PHONY: clean
# clean all the .o and executable files
clean:
rm -rf *.o core *.core $(PROG)
```
---
title: Makefile Syntax
---
### Makefile Syntax
When you are writing a Makefile, most of the time you are writing rules. A typical rule has the form:
```make
# This line contains a comment
target1 target2 ... : prerequisite1 prerequisite2 ...
command 1
command 2
command 3
...
```
- `target` list is a space separated list of files that needs to be "**made**" (created).
- `prerequisite` list is a space separated list of files that the target depends on in some way.
- The `command` list is one or more commands needed to accomplish the task of creating the target. The commands can be any shell command or any program available in your environment.
- **Each command must be indented with a tab.**
- The **#** symbol is used to start a comment in a Makefile.
- Long lines can be continued using the standard Unix escape character backslash (\\).
---
title: Makefile Rules
---
## Rules
There are a number of different kinds of rules:
- **Explicit rules**: specific target, mostly concrete filenames
- **Pattern rules**: wildcards instead of explicit filenames.
- **Implicit rules**: in the rules database built-in to Make
<br/>
### Explicit rules
A rule can have more than one target, and they share the same set of prerequisites:
```make
utils.o main.o: make.h config.h dep.h
```
This is equivalent to:
```make
utils.o: make.h config.h dep.h
main.o: make.h config.h dep.h
```
If any file in the prerequisite list is newer than the target, then `make` will update the target by executing the commands associated with the rule.
---
title: Makefile Rules II
---
### Pattern Rules
Pattern rules let you match the files that have a similar structure:
```c
# Define a pattern rule that compiles every .c file into a .o file
%.o : %.c
$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
```
There is a special rule to generate a file with no suffix (always an executable) from a .c file:
```c
% : %.o
$(LD) $^ $(LDLIBS) -o $@
```
The percent character in a pattern rule is roughly equivalent to * in a Unix shell. It just represents any number of any characters. The percent character can be placed anywhere within the pattern but can occur only once.
```make
# valid uses of %
%,v
s%.o
wrapper_%
```
---
title: Makefile Rules III
---
### Static Pattern Rules
A static pattern rule is one that applies only to a specific list of targets.
```make
OBJECTS = main.o utils.o sort.o
$(OBJECTS): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
```
The only difference between this rule and an ordinary pattern rule is the initial `$(OBJECTS)`: specification.
This limits the rule to the files listed in the `$(OBJECTS)` variable.
Each object file in `$(OBJECTS)` is matched against the pattern `%.o` and its stem is extracted.
Use static pattern rules whenever it is easier to list the target files explicitly than to identify them by a suffix or other pattern.
---
title: Makefile Rules IV
---
### Implicit Rules
The built-in implicit rules are applied whenever a target is being considered and there is no explicit rule to update it.
```make
count_words: counter.o lexer.o
count_words.o: counter.h
counter.o: counter.h lexer.h
```
So using an implicit rule is easy: **simply do not specify a command script when adding your target to the makefile.**
You can use the `--print-data-base` flag to find out all rules that are defined in the internal database.
```bash
make --print-data-base
# or
make -p
```
---
title: Makefile Rules V
---
### Phony Targets
Targets that do not represent files are known as _phony targets_. The typical use case is for a target to be just a label representing a command script.
```make
.PHONY: clean
clean:
rm -f *.o
```
It is important to note that make cannot distinguish between a file target and phony target. So if you happen to have a file named `clean` and a target also named `clean`, then make will associate the file with the phony target.
So, just remember, always specify the phony target with `.PHONY`. (This is a special target in GNU Make).
---
title: Makefile Rules VI
---
### Variables
You can define variables with the following syntax:
```make
# recursively expanded variables
SRC = main.c utils.c
OBJECTS = main.o utils.o
# simply expanded variables
today := 2023-05-11
```
- simply expanded: right-hand side is expanded immediately upon reading the line from the makefile.
- recursively expanded: the right-hand side is expanded when the variable is used.
#### Variable Expansion
Expansion is just a fancy word for getting the value of the variable:
```make
# expansion syntax
$(variable-name)
${variable-name}
```
---
title: Makefile Rules VII
---
#### Automatic Variables
**Automatic variables are set by make after a rule is matched.**
There are several important automatic variables:
```make
$@ The filename representing the target
$? The names of all prerequisites that are newer than the target, separated by spaces
$< The filename of the first prerequisite
$^ The filenames of all the prerequisites, separated by spaces. (duplicates removed)
$+ Similar to $^ but includes duplicates
$* The stem of the target filename, typically a filename without its suffix
```
Here is an example:
```make
# count_words is a executable
count_words: count_words.o counter.o
gcc $^ -o $@
count_words.o: count_words.c
gcc -c $<
counter.o: counter.c
gcc -c $<
```
......@@ -12,9 +12,3 @@ Any questions about:
- Organisation
<br/>
### Materials
- [Concurrency vs Parallelism](https://freecontent.manning.com/concurrency-vs-parallelism/)
- [pthreads(7)](https://man7.org/linux/man-pages/man7/pthreads.7.html)
- [Makefile Tutorial](https://makefiletutorial.com/#getting-started)
......@@ -36,202 +36,3 @@ title: Recap I
See: [MIT 6.005 - Concurrency](http://web.mit.edu/6.005/www/fa16/classes/19-concurrency/)
</v-clicks>
---
title: Recap II
layout: two-cols
---
#### Memory Model
<div class="container flex justify-center mt-10">
<img src="/images/thread-memory-model.png" class="block w-sm"/>
</div>
::right::
All of these threads are independently executing the same program, and they all share the same global memory, including the initialized data, uninitialized data, and heap segments.
A traditional UNIX process is simply a special case of a multithreaded processes; it is a process that contains just one thread.
#### Advantages over Processes
<v-clicks>
- Sharing information between threads is easy and fast.
- Thread creation is faster than process creation—typically, ten times faster or better. (On Linux, threads are implemented using the `clone()` system call)
</v-clicks>
---
title: Recap III
---
#### Pthreads
To compile them, you must include the header `pthread.h` in your code. On the link line, you must also explicitly link with the `pthreads` library, by adding the `-pthread` flag.
```sh
gcc -o main main.c -Wall -pthread
```
#### Thread Creation
```c
// Create a new thread, threads are peers and non-hierarchical
// Which means that you can create threads within threads
int pthread_create(pthread_t* thread, const pthread_attr_t *attr,
void* (*start_routine)(void*), void *arg);
```
#### Thread Completion
In case you want to wait for your threads to complete. _Why do we have a pointer to a pointer here?_
```c
// Waits for the thread identified by thread to terminate.
// You can even wait for a thread inside a thread's start_routine
// If a thread exits but is not joined, it is a "zombie thread".
int pthread_join(pthread_t thread, void ** value_ptr);
```
---
title: Recap IV
---
#### An Example
```c
typedef struct { int a; int b; } arg_t;
typedef struct { int x; int y; } ret_t;
void * mythread(void * arg) {
ret_t * rvals = malloc(sizeof(ret_t));
rvals->x = 1;
rvals->y = 2;
return (void * ) rvals;
}
int main(int argc, char * argv[]) {
pthread_t p;
ret_t * rvals;
arg_t args = { 10, 20 };
if (pthread_create(&p, NULL, mythread, &args)) { return -1; }
if (pthread_join(p, (void **) &rvals)) { return -1; }
printf("returned %d %d\n", rvals->x, rvals->y);
free(rvals);
return 0;
}
```
Why are we using `malloc` and `free` here?
---
title: Recap V
---
### Other Useful Pthreads APIs
#### Thread IDs
```c
// Returns the thread ID of the calling thread
// Thread IDs are used by other Pthreads functions
pthread_t pthread_self(void);
// Check whether two thread IDs are the same.
int pthread_equal(pthread_t t1, pthread_t t2);
```
#### Thread Termination
```c
// equivalent to return
// except it can also be called by functions that are called by the start_routine
void pthread_exit(void *retval);
```
#### Detaching Thread
By default, a thread is **joinable**, meaning that when it terminates, another thread can obtain its return status using `pthread_join()`. Sometimes, we don’t care about the thread’s return status; we simply want the system to automatically clean up and remove the thread when it terminates.
```c
int pthread_detach(pthread_t thread);
// A thread can detach itself in the start_routine
pthread_detach(pthread_self());
```
---
title: Recap VI
layout: two-cols
---
### Thread Synchronization
To avoid race conditions, we must use a _mutex_(short for mutual exclusion) to ensure that only one thread a time can access the shared data.
A mutex has two states: _locked_ and _unlocked_.
When a thread locks a mutex, it becomes the owner of that mutex. Only the mutex owner can unlock the mutex.
**In general, we employ a different mutex for each shared resource.**
::right::
<div class="container flex justify-center mt-10">
<img src="/images/mutex.png" class="block w-sm"/>
</div>
---
title: Recap VII
---
#### Statically Allocated Mutexes
A mutex can either be allocated as a static variable or be created dynamically at run time (for example using `malloc()`).
A mutex is a variable of the type `pthread_mutex_t`. Before it can be used, a mutex must always be initialized.
```c
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
```
<br/>
#### Dynamically Initializing a Mutex
The static initializer value `PTHREAD_MUTEX_INITIALIZER` can be used only for initializing a statically allocated mutex with default attributes.
```c
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
```
---
title: Recap VIII
---
#### Locking and Unlocking a Mutex
After initialization, a mutex is unlocked. You can use the following functions to lock and unlock the mutex:
```c
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
```
**They always appear in pairs.**
#### Destroying a Mutex
When an automatically or dynamically allocated mutex is no longer required, it should be destroyed using `pthread_mutex_destroy()`.
```c
int pthread_mutex_destroy(pthread_mutex_t *mutex);
```
**Note:** It is not necessary to call `pthread_mutex_destroy()` on a mutex that was statically initialized using `PTHREAD_MUTEX_INITIALIZER`.
......@@ -16,7 +16,7 @@ transition: fade-out
css: unocss
---
# ALP4 Tutorial 3
# ALP4 Tutorial 4
## Chao Zhan
......@@ -34,11 +34,6 @@ transition: slide-left
src: ./pages/recap.md
---
---
transition: slide-left
src: ./pages/makefile.md
---
---
transition: slide-left
src: ./pages/qa.md
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment