Michael Mächtel 8 anni fa
parent
commit
9763eb459f

+ 27
- 0
hw8/README.md Vedi File

@@ -0,0 +1,27 @@
1
+# hw8
2
+
3
+## Tasks
4
+
5
+To fulfill **hw8** you have to solve:
6
+
7
+- task1
8
+- simu1
9
+- simu2
10
+
11
+## Files
12
+
13
+You find already or reuse some files for rust tasks. Please remember to use cargo to create a new project for each task and copy the given files into their dedicated directories.
14
+
15
+## Pull-Request
16
+
17
+Please merge any accepted reviews into your branch. If you are ready with the homework, all tests run, please create a pull request named **hw8**.
18
+
19
+## Credits of hw8
20
+
21
+| Task     | max. Credits | Comment                 |
22
+| -------- | ------------ | ----------------------- |
23
+| task1    | 3            | `ANSWERS.md` important! |
24
+| simu1    | 0,5          |                         |
25
+| simu2    | 0,5          |                         |
26
+| Deadline | +1           |                         |
27
+| =        | 5            |                         |

+ 23
- 0
hw8/simu1/Makefile Vedi File

@@ -0,0 +1,23 @@
1
+
2
+FLAGS = -Wall -pthread -g
3
+
4
+all: main-race main-deadlock main-deadlock-global main-signal main-signal-cv
5
+
6
+clean:
7
+	rm -f main-race main-deadlock main-deadlock-global main-signal main-signal-cv
8
+
9
+main-race: main-race.c mythreads.h
10
+	gcc -o main-race main-race.c $(FLAGS)
11
+
12
+main-deadlock: main-deadlock.c mythreads.h
13
+	gcc -o main-deadlock main-deadlock.c $(FLAGS)
14
+
15
+main-deadlock-global: main-deadlock-global.c mythreads.h
16
+	gcc -o main-deadlock-global main-deadlock-global.c $(FLAGS)
17
+
18
+main-signal: main-signal.c mythreads.h
19
+	gcc -o main-signal main-signal.c $(FLAGS)
20
+
21
+main-signal-cv: main-signal-cv.c mythreads.h
22
+	gcc -o main-signal-cv main-signal-cv.c $(FLAGS)
23
+

+ 66
- 0
hw8/simu1/QUESTIONS.md Vedi File

@@ -0,0 +1,66 @@
1
+# QUESTIONS: Thread Real Posix API
2
+
3
+In this section, we’ll write some simple multi-threaded programs and use a
4
+specific tool, called helgrind, to find problems in these programs.
5
+
6
+Read the README in the homework download for details on how to build the
7
+programs and run helgrind.
8
+
9
+## Questions
10
+
11
+Do not copy/paste all output of valgrind, only the parts, where your answers
12
+rely on. Run your tests on the bsys container (not your notebook!).
13
+
14
+1.  First build `main-race.c`. Examine the code so you can see the (hopefully
15
+    obvious) data race in the code. Now run helgrind (by typing **valgrind
16
+    \--tool=helgrind main-race**) to see how it reports the race. Does it point to
17
+    the right lines of code? What other information does it give to you?
18
+
19
+1.  Correkt your code:
20
+
21
+    1. What happens when you remove one of the offending lines of code?
22
+
23
+    1. Now add a lock around one of the updates to the shared variable,
24
+
25
+    1. and then around both.
26
+
27
+    1. What does helgrind report in each (2.1, 2.2 and 2.3) of these cases? 
28
+
29
+    Checkin your corrected file `main-race.`
30
+
31
+1.  Now let’s look at `main-deadlock.c`. Examine the code. This code has a
32
+    problem known as deadlock (which we discuss in much more depth in a
33
+    forthcoming chapter). Can you see what problem it might have? Does the
34
+    program run? Explain!
35
+
36
+1.  Now run helgrind on this code. What does helgrind report? Do not copy the
37
+    output into your answers, explain what helgrind tells you!
38
+
39
+1.  Now run helgrind on `main-deadlock-global.c`. 
40
+
41
+    1. Examine the code; does it have the same problem that `main-deadlock.c`
42
+    has?
43
+
44
+    1. Run the program. 
45
+
46
+    1. Should helgrind be reporting the same error? 
47
+
48
+    1. What does this tell you about tools like helgrind?
49
+
50
+1.  Let’s next look at `main-signal.c`. This code uses a variable (done) to
51
+    signal that the child is done and that the parent can now continue. Why is
52
+    this code inefficient? (what does the parent end up spending its time doing,
53
+    particularly if the child thread takes a long time to complete?)
54
+
55
+1.  Now run helgrind on this program. 
56
+    1.  What does it report? 
57
+    1.  Is the code correct?
58
+
59
+1.  Now look at a slightly modified version of the code, which is found in
60
+    `main-signal-cv.c`. This version uses a condition variable to do the
61
+    signaling (and associated lock). 
62
+
63
+     1. Why is this code preferred to the previous version? 
64
+     1. Is it correctness, or performance, or both?
65
+
66
+1.  Once again run helgrind on **main-signal-cv**. Does it report any errors?

+ 37
- 0
hw8/simu1/README.md Vedi File

@@ -0,0 +1,37 @@
1
+# README Synchronisation: Real POSIX API
2
+
3
+In this homework, you'll use a real tool on Linux to find problems in
4
+multi-threaded code. The tool is called **helgrind** (available as part of the
5
+valgrind suite of debugging tools).
6
+
7
+See <http://valgrind.org/docs/manual/hg-manual.html> for details about the tool,
8
+including how to download and install it (if it's not already on your Linux
9
+system).
10
+
11
+You'll then look at a number of multi-threaded C programs to see how you can
12
+use the tool to debug problematic threaded code.
13
+
14
+Then, type **make** to build all the different programs. Examine the Makefile
15
+for more details on how that works.
16
+
17
+Then, you have a few different C programs to look at:
18
+
19
+-   `main-race.c`:
20
+    A simple race condition
21
+
22
+-   `main-deadlock.c`:
23
+    A simple deadlock
24
+
25
+-   `main-deadlock-global.c`:
26
+    A solution to the deadlock problem
27
+
28
+-   `main-signal.c`:
29
+    A simple child/parent signaling example
30
+
31
+-   `main-signal-cv.c`:
32
+    A more efficient signaling via condition variables
33
+
34
+-   `mythreads.h`:
35
+    Header file with wrappers to make code check errors and be more readable
36
+
37
+With these programs, you can now answer the questions in the textbook.

+ 31
- 0
hw8/simu1/main-deadlock-global.c Vedi File

@@ -0,0 +1,31 @@
1
+#include <stdio.h>
2
+
3
+#include "mythreads.h"
4
+
5
+pthread_mutex_t g = PTHREAD_MUTEX_INITIALIZER;
6
+pthread_mutex_t m1 = PTHREAD_MUTEX_INITIALIZER;
7
+pthread_mutex_t m2 = PTHREAD_MUTEX_INITIALIZER;
8
+
9
+void* worker(void* arg) {
10
+    Pthread_mutex_lock(&g);
11
+    if ((long long) arg == 0) {
12
+	Pthread_mutex_lock(&m1);
13
+	Pthread_mutex_lock(&m2);
14
+    } else {
15
+	Pthread_mutex_lock(&m2);
16
+	Pthread_mutex_lock(&m1);
17
+    }
18
+    Pthread_mutex_unlock(&m1);
19
+    Pthread_mutex_unlock(&m2);
20
+    Pthread_mutex_unlock(&g);
21
+    return NULL;
22
+}
23
+
24
+int main(int argc, char *argv[]) {
25
+    pthread_t p1, p2;
26
+    Pthread_create(&p1, NULL, worker, (void *) (long long) 0);
27
+    Pthread_create(&p2, NULL, worker, (void *) (long long) 1);
28
+    Pthread_join(p1, NULL);
29
+    Pthread_join(p2, NULL);
30
+    return 0;
31
+}

+ 28
- 0
hw8/simu1/main-deadlock.c Vedi File

@@ -0,0 +1,28 @@
1
+#include <stdio.h>
2
+
3
+#include "mythreads.h"
4
+
5
+pthread_mutex_t m1 = PTHREAD_MUTEX_INITIALIZER;
6
+pthread_mutex_t m2 = PTHREAD_MUTEX_INITIALIZER;
7
+
8
+void* worker(void* arg) {
9
+    if ((long long) arg == 0) {
10
+	Pthread_mutex_lock(&m1);
11
+	Pthread_mutex_lock(&m2);
12
+    } else {
13
+	Pthread_mutex_lock(&m2);
14
+	Pthread_mutex_lock(&m1);
15
+    }
16
+    Pthread_mutex_unlock(&m1);
17
+    Pthread_mutex_unlock(&m2);
18
+    return NULL;
19
+}
20
+
21
+int main(int argc, char *argv[]) {
22
+    pthread_t p1, p2;
23
+    Pthread_create(&p1, NULL, worker, (void *) (long long) 0);
24
+    Pthread_create(&p2, NULL, worker, (void *) (long long) 1);
25
+    Pthread_join(p1, NULL);
26
+    Pthread_join(p2, NULL);
27
+    return 0;
28
+}

+ 18
- 0
hw8/simu1/main-race.c Vedi File

@@ -0,0 +1,18 @@
1
+#include <stdio.h>
2
+
3
+#include "mythreads.h"
4
+
5
+int balance = 0;
6
+
7
+void* worker(void* arg) {
8
+    balance++; // unprotected access
9
+    return NULL;
10
+}
11
+
12
+int main(int argc, char *argv[]) {
13
+    pthread_t p;
14
+    Pthread_create(&p, NULL, worker, NULL);
15
+    balance++; // unprotected access
16
+    Pthread_join(p, NULL);
17
+    return 0;
18
+}

+ 55
- 0
hw8/simu1/main-signal-cv.c Vedi File

@@ -0,0 +1,55 @@
1
+#include <stdio.h>
2
+
3
+#include "mythreads.h"
4
+
5
+// 
6
+// simple synchronizer: allows one thread to wait for another
7
+// structure "synchronizer_t" has all the needed data
8
+// methods are:
9
+//   init (called by one thread)
10
+//   wait (to wait for a thread)
11
+//   done (to indicate thread is done)
12
+// 
13
+typedef struct __synchronizer_t {
14
+    pthread_mutex_t lock;
15
+    pthread_cond_t cond;
16
+    int done;
17
+} synchronizer_t;
18
+
19
+synchronizer_t s;
20
+
21
+void signal_init(synchronizer_t *s) {
22
+    Pthread_mutex_init(&s->lock, NULL);
23
+    Pthread_cond_init(&s->cond, NULL);
24
+    s->done = 0;
25
+}
26
+
27
+void signal_done(synchronizer_t *s) {
28
+    Pthread_mutex_lock(&s->lock);
29
+    s->done = 1;
30
+    Pthread_cond_signal(&s->cond);
31
+    Pthread_mutex_unlock(&s->lock);
32
+}
33
+
34
+void signal_wait(synchronizer_t *s) {
35
+    Pthread_mutex_lock(&s->lock);
36
+    while (s->done == 0)
37
+	Pthread_cond_wait(&s->cond, &s->lock);
38
+    Pthread_mutex_unlock(&s->lock);
39
+}
40
+
41
+void* worker(void* arg) {
42
+    printf("this should print first\n");
43
+    signal_done(&s);
44
+    return NULL;
45
+}
46
+
47
+int main(int argc, char *argv[]) {
48
+    pthread_t p;
49
+    signal_init(&s);
50
+    Pthread_create(&p, NULL, worker, NULL);
51
+    signal_wait(&s);
52
+    printf("this should print last\n");
53
+
54
+    return 0;
55
+}

+ 20
- 0
hw8/simu1/main-signal.c Vedi File

@@ -0,0 +1,20 @@
1
+#include <stdio.h>
2
+
3
+#include "mythreads.h"
4
+
5
+int done = 0;
6
+
7
+void* worker(void* arg) {
8
+    printf("this should print first\n");
9
+    done = 1;
10
+    return NULL;
11
+}
12
+
13
+int main(int argc, char *argv[]) {
14
+    pthread_t p;
15
+    Pthread_create(&p, NULL, worker, NULL);
16
+    while (done == 0)
17
+	;
18
+    printf("this should print last\n");
19
+    return 0;
20
+}

+ 62
- 0
hw8/simu1/mythreads.h Vedi File

@@ -0,0 +1,62 @@
1
+#ifndef __MYTHREADS_h__
2
+#define __MYTHREADS_h__
3
+
4
+#include <pthread.h>
5
+#include <assert.h>
6
+#include <stdlib.h>
7
+#include <sys/time.h>
8
+
9
+double Time_GetSeconds() {
10
+    struct timeval t;
11
+    int rc = gettimeofday(&t, NULL);
12
+    assert(rc == 0);
13
+    return (double) ((double)t.tv_sec + (double)t.tv_usec / 1e6);
14
+}
15
+
16
+void Pthread_mutex_init(pthread_mutex_t *mutex,
17
+                        const pthread_mutexattr_t *attr) {
18
+    int rc = pthread_mutex_init(mutex, attr);
19
+    assert(rc == 0);
20
+}
21
+
22
+void Pthread_mutex_lock(pthread_mutex_t *m) {
23
+    int rc = pthread_mutex_lock(m);
24
+    assert(rc == 0);
25
+}
26
+
27
+void Pthread_mutex_unlock(pthread_mutex_t *m) {
28
+    int rc = pthread_mutex_unlock(m);
29
+    assert(rc == 0);
30
+}
31
+
32
+void Pthread_cond_init(pthread_cond_t *cond,
33
+		       const pthread_condattr_t *attr) {
34
+    int rc = pthread_cond_init(cond, attr);
35
+    assert(rc == 0);
36
+}
37
+
38
+void Pthread_cond_wait(pthread_cond_t *cond,
39
+		       pthread_mutex_t *mutex) {
40
+    int rc = pthread_cond_wait(cond, mutex);
41
+    assert(rc == 0);
42
+}
43
+
44
+void Pthread_cond_signal(pthread_cond_t *cond) {
45
+    int rc = pthread_cond_signal(cond);
46
+    assert(rc == 0);
47
+}
48
+
49
+void Pthread_create(pthread_t *thread, const pthread_attr_t *attr,
50
+                    void *(*start_routine)(void*), void *arg) {
51
+    int rc = pthread_create(thread, attr, start_routine, arg);
52
+    assert(rc == 0);
53
+}
54
+
55
+void Pthread_join(pthread_t thread, void **value_ptr) {
56
+    int rc = pthread_join(thread, value_ptr);
57
+    assert(rc == 0);
58
+}
59
+
60
+
61
+
62
+#endif // __MYTHREADS_h__

+ 24
- 0
hw8/simu2/Makefile Vedi File

@@ -0,0 +1,24 @@
1
+
2
+BINARIES = main-two-cvs-while main-two-cvs-if main-one-cv-while main-two-cvs-while-extra-unlock
3
+HEADERS = mythreads.h main-header.h main-common.c pc-header.h
4
+
5
+CC=gcc -Werror
6
+
7
+all: $(BINARIES)
8
+
9
+clean:
10
+	rm -f $(BINARIES)
11
+
12
+main-one-cv-while: main-one-cv-while.c $(HEADERS)
13
+	$(CC) -o main-one-cv-while main-one-cv-while.c -Wall -pthread
14
+
15
+main-two-cvs-if: main-two-cvs-if.c $(HEADERS)
16
+	$(CC) -o main-two-cvs-if main-two-cvs-if.c -Wall -pthread
17
+
18
+main-two-cvs-while: main-two-cvs-while.c $(HEADERS)
19
+	$(CC) -o main-two-cvs-while main-two-cvs-while.c -Wall -pthread
20
+
21
+main-two-cvs-while-extra-unlock: main-two-cvs-while-extra-unlock.c $(HEADERS)
22
+	$(CC) -o main-two-cvs-while-extra-unlock main-two-cvs-while-extra-unlock.c -Wall -pthread
23
+
24
+

+ 108
- 0
hw8/simu2/QUESTIONS.md Vedi File

@@ -0,0 +1,108 @@
1
+# QUESTIONS Synchronisation: CV
2
+
3
+This homework lets you explore some real code that uses locks and condition
4
+variables to implement various forms of the producer/consumer queue discussed in
5
+the chapter. You’ll look at the real code, run it in various configurations, and
6
+use it to learn about what works and what doesn’t, as well as other intricacies.
7
+
8
+The different versions of the code correspond to different ways to “solve” the
9
+producer/consumer problem. Most are incorrect; one is correct. Read the chapter
10
+to learn more about what the producer/consumer problem is, and what the code
11
+generally does.
12
+
13
+The first step is to download the code and type make to build all the variants.
14
+You should see four:
15
+
16
+-   `main-one-cv-while.c`: The producer/consumer problem solved with a single
17
+    condition variable.
18
+
19
+-   `main-two-cvs-if.c`: Same but with two condition variables and using an if to
20
+    check whether to sleep.
21
+
22
+-   `main-two-cvs-while.c`: Same but with two condition variables and while to
23
+    check whether to sleep. This is the correct version.
24
+
25
+-   `main-two-cvs-while-extra-unlock.c`: Same but releasing the lock and then
26
+    reacquiring it around the fill and get routines.
27
+
28
+It’s also useful to look at `pc-header.h` which contains common code for all of
29
+these different main programs, and the Makefile so as to build the code
30
+properly.
31
+
32
+See the `README.md` for details on these programs.
33
+
34
+## Questions
35
+
36
+Please explain your answers, even if not asked for it. Try to think first about
37
+the question, then run the code. Remember, in the exam you can't run the code,
38
+but have to think about the question and finally answer it correctly.
39
+
40
+### Fix Source Code
41
+
42
+When you compile the programs on your workstation, you get a lot of warnings
43
+which are turned into errors. You have to correct these errors first. Check, by
44
+which parameters a thread is created, and how the id is casted later on.
45
+
46
+0.  Explain the problem, and how you fixed it.
47
+
48
+### `main-two-cvs-while.c`
49
+
50
+Our first questions focus on `main-two-cvs-while.c` (the working solution).
51
+First, study the code. Do you think you have an understanding of what should
52
+happen when you run the program?
53
+
54
+1.  Now run with one producer and one consumer, and have the producer produce a
55
+    few values. Start with a buffer of size 1, and then increase it. How does the
56
+    behavior of the code change when the buffer is larger? (or does it?)
57
+
58
+1.  What would you predict num full to be with different buffer sizes (e.g., -m
59
+    10) and different numbers of produced items (e.g., -l 100), when you change
60
+    the consumer sleep string from default (no sleep) to -C 0,0,0,0,0,0,1?
61
+
62
+1.  If possible, run the code on different systems (e.g., Mac OS X , Linux, lab
63
+    container and your notebook). Do you see different behavior across these
64
+    systems?
65
+
66
+1.  Let’s look at some timings of different runs. How long do you think the
67
+    following execution, with one producer, three consumers, a single-entry
68
+    shared buffer, and each consumer pausing at point c3 for a second, will take?
69
+
70
+      prompt> ./main-two-cvs-while -p 1 -c 3 -m 1 -C
71
+              0,0,0,1,0,0,0:0,0,0,1,0,0,0:0,0,0,1,0,0,0 -l 10 -v -t
72
+
73
+1.  Now change the size of the shared buffer to 3(-m3). Will this make any
74
+    difference in the total time?
75
+
76
+1.  Now change the location of the sleep to c6 (this models a consumer taking
77
+    something off the queue and then doing something with it for a while), again
78
+    using a single-entry buffer. What time do you predict in this case?
79
+
80
+    ```text
81
+     prompt> ./main-two-cvs-while -p 1 -c 3 -m 1 -C
82
+             0,0,0,0,0,0,1:0,0,0,0,0,0,1:0,0,0,0,0,0,1 -l 10 -v -t
83
+    ```
84
+
85
+1.  Finally, change the buffer size to 3 again (-m 3). What time do you predict
86
+    now?
87
+
88
+### `main-one-cv-while.c`
89
+
90
+1.  Now let’s look at `main-one-cv-while.c`. Can you configure a sleep string,
91
+    assuming a single producer, one consumer, and a buffer of size 1, to cause a
92
+    problem with this code? Explain why this happens!
93
+
94
+1.  Now change the number of consumers to two. Can you construct sleep strings
95
+    for the producer and the consumers so as to cause a problem in the code?
96
+    Explain why this happens!
97
+
98
+### `main-two-cvs-if.c`
99
+
100
+1.  Now examine `main-two-cvs-if.c`. Can you cause a problem to happen in this
101
+    code? Again consider the case where there is only one consumer, and then the
102
+    case where there is more than one. Explain the problematic code sequence. 
103
+
104
+### `main-two-cvs-while-extra-unlock.c`
105
+
106
+1.  Finally, examine `main-two-cvs-while-extra-unlock.c`. What problem arises
107
+    when you release the lock before doing a put or a get? Can you reliably cause
108
+    such a problem to happen, given the sleep strings? What bad thing can happen?

+ 237
- 0
hw8/simu2/README.md Vedi File

@@ -0,0 +1,237 @@
1
+# README Synchronisation: CV
2
+
3
+This homework lets you explore some real code that uses locks and condition
4
+variables to implement various forms of the producer/consumer queue discussed in
5
+the chapter. You'll look at the real code, run it in various configurations, and
6
+use it to learn about what works and what doesn't, as well as other intricacies.
7
+
8
+The different versions of the code correspond to different ways to "solve" the
9
+producer/consumer problem. Most are incorrect; one is correct.  Read the chapter
10
+to learn more about what the producer/consumer problem is, and what the code
11
+generally does.
12
+
13
+The first step is to download the code and type make to build all the variants.
14
+You should see four:
15
+
16
+-   `main-one-cv-while.c`: The producer/consumer problem solved with a single
17
+    condition variable.
18
+
19
+-   `main-two-cvs-if.c`: Same but with two condition variables and using an if to
20
+    check whether to sleep.
21
+
22
+-   `main-two-cvs-while.c`: Same but with two condition variables and while to
23
+    check whether to sleep. This is the correct version.
24
+
25
+-   `main-two-cvs-while-extra-unlock.c`: Same but releasing the lock and then
26
+    reacquiring it around the fill and get routines.
27
+
28
+It's also useful to look at `pc-header.h` which contains common code for all of
29
+these different main programs, and the Makefile so as to build the code
30
+properly.
31
+
32
+Each program takes the following flags:
33
+
34
+```text
35
+-l <number of items each producer produces>
36
+
37
+-m <size of the shared producer/consumer buffer>
38
+
39
+-p <number of producers>
40
+
41
+-c <number of consumers>
42
+
43
+-P <sleep string: how producer should sleep at various points>
44
+
45
+-C <sleep string: how consumer should sleep at various points>
46
+
47
+-v verbose flag: trace what is happening and print it]
48
+
49
+-t [timing flag: time entire execution and print total time]
50
+```
51
+
52
+The first four arguments are relatively self-explanatory: -l specifies how many
53
+times each producer should loop (and thus how many data items each producer
54
+produces), -m controls the size of the shared buffer (greater than or equal to
55
+one), and -p and -c set how many producers and consumers there are,
56
+respectively.
57
+
58
+What is more interesting are the two sleep strings, one for producers, and one
59
+for consumers. These flags allow you to make each thread sleep at certain points
60
+in an execution and thus switch to other threads; doing so allows you to play
61
+with each solution and perhaps pinpoint specific problems or study other facets
62
+of the producer/consumer problem.
63
+
64
+The string is specified as follows. If there are three producers, for example,
65
+the string should specify sleep times for each producer separately, with a colon
66
+separator. The sleep string for these three producers would look something like
67
+this:
68
+
69
+```text
70
+  sleep_string_for_p0:sleep_string_for_p1:sleep_string_for_p2
71
+```
72
+
73
+Each sleep string, in turn, is a comma-separated list, which is used to decide
74
+how much to sleep at each sleep point in the code. To understand this
75
+per-producer or per-consumer sleep string better, let's look at the code in
76
+`main-two-cvs-while.c`, specifically at the producer code. In this code snippet,
77
+a producer thread loops for a while, putting elements into the shared buffer via
78
+calls to do_fill(). Around this filling routine are some waiting and signaling
79
+code to ensure the buffer is not full when the producer tries to fill it. See
80
+the chapter for more details.
81
+
82
+```c
83
+void *producer(void *arg) {
84
+    int id = (int) arg;
85
+    int i;
86
+    for (i = 0; i < loops; i++) {   p0;
87
+        Mutex_lock(&m);             p1;
88
+        while (num_full == max) {   p2;
89
+            Cond_wait(&empty, &m);  p3;
90
+        }
91
+        do_fill(i);                 p4;
92
+        Cond_signal(&fill);         p5;
93
+        Mutex_unlock(&m);           p6;
94
+    }
95
+    return NULL;
96
+}
97
+```
98
+
99
+As you can see from the code, there are a number of points labeled p0, p1, etc.
100
+These points are where the code can be put to sleep. The consumer code (not
101
+shown here) has similar wait points (c0, etc.).
102
+
103
+Specifying a sleep string for a producer is easy: just identify how long the
104
+producer should sleep at each point p0, p1, ..., p6. For example, the string
105
+1,0,0,0,0,0,0 specifies that the producer should sleep for 1 second at marker p0
106
+(before grabbing the lock), and then not at all each time through the loop.
107
+
108
+Now let's show the output of running one of these programs.  To begin, let's
109
+assume that the user runs with just one producer and one consumer. Let's not add
110
+any sleeping at all (this is the default behavior). The buffer size, in this
111
+example, is set to 2 (-m 2).
112
+
113
+First, let's build the code:
114
+
115
+```text
116
+prompt> make
117
+gcc -o main-two-cvs-while main-two-cvs-while.c -Wall -pthread
118
+gcc -o main-two-cvs-if main-two-cvs-if.c -Wall -pthread
119
+gcc -o main-one-cv-while main-one-cv-while.c -Wall -pthread
120
+gcc -o main-two-cvs-while-extra-unlock main-two-cvs-while-extra-unlock.c
121
+  -Wall -pthread
122
+```
123
+
124
+Now we can run something:
125
+
126
+```text
127
+prompt> ./main-two-cvs-while -l 3 -m 2 -p 1 -c 1 -v
128
+```
129
+
130
+In this case, if you trace the code (with the verbose flag, -v), you will get
131
+the following output (or something like it) on the screen:
132
+
133
+```text
134
+ NF             P0 C0
135
+  0 [*---  --- ] p0
136
+  0 [*---  --- ]    c0
137
+  0 [*---  --- ] p1
138
+  1 [u  0 f--- ] p4
139
+  1 [u  0 f--- ] p5
140
+  1 [u  0 f--- ] p6
141
+  1 [u  0 f--- ] p0
142
+  1 [u  0 f--- ]    c1
143
+  0 [ --- *--- ]    c4
144
+  0 [ --- *--- ]    c5
145
+  0 [ --- *--- ]    c6
146
+  0 [ --- *--- ]    c0
147
+  0 [ --- *--- ] p1
148
+  1 [f--- u  1 ] p4
149
+  1 [f--- u  1 ] p5
150
+  1 [f--- u  1 ] p6
151
+  1 [f--- u  1 ] p0
152
+  1 [f--- u  1 ]    c1
153
+  0 [*---  --- ]    c4
154
+  0 [*---  --- ]    c5
155
+  0 [*---  --- ]    c6
156
+  0 [*---  --- ]    c0
157
+  0 [*---  --- ] p1
158
+  1 [u  2 f--- ] p4
159
+  1 [u  2 f--- ] p5
160
+  1 [u  2 f--- ] p6
161
+  1 [u  2 f--- ]    c1
162
+  0 [ --- *--- ]    c4
163
+  0 [ --- *--- ]    c5
164
+  0 [ --- *--- ]    c6
165
+  1 [f--- uEOS ] [main: added end-of-stream marker]
166
+  1 [f--- uEOS ]    c0
167
+  1 [f--- uEOS ]    c1
168
+  0 [*---  --- ]    c4
169
+  0 [*---  --- ]    c5
170
+  0 [*---  --- ]    c6
171
+
172
+Consumer consumption:
173
+  C0 -> 3
174
+```
175
+
176
+Before describing what is happening in this simple example, let's understand the
177
+depiction of the shared buffer in the output, as is shown on the left. At first
178
+it is empty (an empty slot indicated by ---, and the two empty slots shown as
179
+[*--- --- ]); the output also shows the number of entries in the buffer
180
+(num_full), which starts at 0. Then, after P0 puts a value in it, its state
181
+changes to [u 0 f--- ](<and num_full changes to 1>). You might also notice a few
182
+additional markers here; the u marker shows where the use_ptr is (this is where
183
+the next consumer to consume a value will get something from); similarly, the f
184
+marker shows where the fill_ptr is (this is where the next producer will
185
+produce a value). When you see the \* marker, it just means the use_ptr and
186
+fill_ptr are pointing to the same slot.
187
+
188
+Along the right you can see the trace of which step each producer and consumer
189
+is about to execute. For example, the producer grabs the lock (p1), and then,
190
+because the buffer has an empty slot, produces a value into it (p4). It then
191
+continues until the point where it releases the lock (p6) and then tries to
192
+reacquire it. In this example, the consumer acquires the lock instead and
193
+consumes the value (c1, c4). Study the trace further to understand how the
194
+producer/consumer solution works with a single producer and consumer.
195
+
196
+Now let's add some pauses to change the behavior of the trace. In this case,
197
+let's say we want to make the producer sleep so that the consumer can run first.
198
+We can accomplish this as follows:
199
+
200
+```text
201
+prompt> ./main-two-cvs-while -l 1 -m 2 -p 1 -c 1 -P 1,0,0,0,0,0,0 -C 0 -v
202
+```
203
+
204
+```text
205
+The results:
206
+ NF             P0 C0
207
+  0 [*---  --- ] p0
208
+  0 [*---  --- ]    c0
209
+  0 [*---  --- ]    c1
210
+  0 [*---  --- ]    c2
211
+  0 [*---  --- ] p1
212
+  1 [u  0 f--- ] p4
213
+  1 [u  0 f--- ] p5
214
+  1 [u  0 f--- ] p6
215
+  1 [u  0 f--- ] p0
216
+  1 [u  0 f--- ]    c3
217
+  0 [ --- *--- ]    c4
218
+  0 [ --- *--- ]    c5
219
+  0 [ --- *--- ]    c6
220
+  0 [ --- *--- ]    c0
221
+  0 [ --- *--- ]    c1
222
+  0 [ --- *--- ]    c2
223
+ ...
224
+```
225
+
226
+As you can see, the producer hits the p0 marker in the code and then grabs the
227
+first value from its sleep specification, which in this case is 1, and thus each
228
+sleeps for 1 second before even trying to grab the lock. Thus, the consumer gets
229
+to run, grabs the lock, but finds the queue empty, and thus sleeps (releasing
230
+the lock). The producer then runs (eventually), and all proceeds as you might
231
+expect.
232
+
233
+Do note: a sleep specification must be given for each producer and consumer.
234
+Thus, if you create two producers and three consumers (with -p 2 -c 3, you must
235
+specify sleep strings for each (e.g., -P 0:1 or -C 0,1,2:0:3,3,3,1,1,1). Sleep
236
+strings can be shorter than the number of sleep points in the code; the
237
+remaining sleep slots are initialized to be zero.

+ 136
- 0
hw8/simu2/main-common.c Vedi File

@@ -0,0 +1,136 @@
1
+// Common usage() prints out stuff for command-line help
2
+void usage() {
3
+    fprintf(stderr, "usage: \n");
4
+    fprintf(stderr, "  -l <number of items each producer produces>\n");
5
+    fprintf(stderr, "  -m <size of the shared producer/consumer buffer>\n");
6
+    fprintf(stderr, "  -p <number of producers>\n");
7
+    fprintf(stderr, "  -c <number of consumers>\n");
8
+    fprintf(stderr, "  -P <sleep string: how each producer should sleep at various points in execution>\n");
9
+    fprintf(stderr, "  -C <sleep string: how each consumer should sleep at various points in execution>\n");
10
+    fprintf(stderr, "  -v [ verbose flag: trace what is happening and print it ]\n");
11
+    fprintf(stderr, "  -t [ timing flag: time entire execution and print total time ]\n");
12
+    exit(1);
13
+}
14
+
15
+// Common main() for all four programs
16
+// - Does arg parsing
17
+// - Starts producers and consumers
18
+// - Once producers are finished, puts END_OF_STREAM
19
+//   marker into shared queue to signal end to consumers
20
+// - Then waits for consumers and prints some final info
21
+int main(int argc, char *argv[]) {
22
+    loops = 1;
23
+    max = 1;
24
+    consumers = 1;
25
+    producers = 1;
26
+
27
+    char *producer_pause_string = NULL;
28
+    char *consumer_pause_string = NULL;
29
+
30
+    opterr = 0;
31
+    int c;
32
+    while ((c = getopt (argc, argv, "l:m:p:c:P:C:vt")) != -1) {
33
+	switch (c) {
34
+	case 'l':
35
+	    loops = atoi(optarg);
36
+	    break;
37
+	case 'm':
38
+	    max = atoi(optarg);
39
+	    break;
40
+	case 'p':
41
+	    producers = atoi(optarg);
42
+	    break;
43
+	case 'c':
44
+	    consumers = atoi(optarg);
45
+	    break;
46
+	case 'P':
47
+	    producer_pause_string = optarg;
48
+	    break;
49
+	case 'C':
50
+	    consumer_pause_string = optarg;
51
+	    break;
52
+	case 'v':
53
+	    do_trace = 1;
54
+	    break;
55
+	case 't':
56
+	    do_timing = 1;
57
+	    break;
58
+	default:
59
+	    usage();
60
+	}
61
+    }
62
+
63
+    assert(loops > 0);
64
+    assert(max > 0);
65
+    assert(producers <= MAX_THREADS);
66
+    assert(consumers <= MAX_THREADS);
67
+
68
+    if (producer_pause_string != NULL)
69
+	parse_pause_string(producer_pause_string, "producers", producers, producer_pause_times);
70
+    if (consumer_pause_string != NULL) 
71
+	parse_pause_string(consumer_pause_string, "consumers", consumers, consumer_pause_times);
72
+
73
+    // make space for shared buffer, and init it ...
74
+    buffer = (int *) Malloc(max * sizeof(int));
75
+    int i;
76
+    for (i = 0; i < max; i++) {
77
+	buffer[i] = EMPTY;
78
+    }
79
+
80
+    do_print_headers();
81
+
82
+    double t1 = Time_GetSeconds();
83
+
84
+    // start up all threads; order doesn't matter here
85
+    pthread_t pid[MAX_THREADS], cid[MAX_THREADS];
86
+    int thread_id = 0;
87
+    for (i = 0; i < producers; i++) {
88
+	Pthread_create(&pid[i], NULL, producer, (void *) (long long) thread_id); 
89
+	thread_id++;
90
+    }
91
+    for (i = 0; i < consumers; i++) {
92
+	Pthread_create(&cid[i], NULL, consumer, (void *) (long long) thread_id); 
93
+	thread_id++;
94
+    }
95
+
96
+    // now wait for all PRODUCERS to finish
97
+    for (i = 0; i < producers; i++) {
98
+	Pthread_join(pid[i], NULL); 
99
+    }
100
+
101
+    // end case: when producers are all done
102
+    // - put "consumers" number of END_OF_STREAM's in queue
103
+    // - when consumer sees -1, it exits
104
+    for (i = 0; i < consumers; i++) {
105
+	Mutex_lock(&m);
106
+	while (num_full == max) 
107
+	    Cond_wait(empty_cv, &m);
108
+	do_fill(END_OF_STREAM);
109
+	do_eos();
110
+	Cond_signal(fill_cv);
111
+	Mutex_unlock(&m);
112
+    }
113
+
114
+    // now OK to wait for all consumers
115
+    int counts[consumers];
116
+    for (i = 0; i < consumers; i++) {
117
+	Pthread_join(cid[i], (void *) &counts[i]); 
118
+    }
119
+
120
+    double t2 = Time_GetSeconds();
121
+
122
+    if (do_trace) {
123
+	printf("\nConsumer consumption:\n");
124
+	for (i = 0; i < consumers; i++) {
125
+	    printf("  C%d -> %d\n", i, counts[i]);
126
+	}
127
+	printf("\n");
128
+    }
129
+
130
+    if (do_timing) {
131
+	printf("Total time: %.2f seconds\n", t2-t1);
132
+    }
133
+
134
+    return 0;
135
+}
136
+

+ 171
- 0
hw8/simu2/main-header.h Vedi File

@@ -0,0 +1,171 @@
1
+#ifndef __main_header_h__
2
+#define __main_header_h__
3
+
4
+// all this is for the homework side of things
5
+
6
+int do_trace = 0;
7
+int do_timing = 0;
8
+
9
+#define p0 do_pause(id, 1, 0, "p0"); 
10
+#define p1 do_pause(id, 1, 1, "p1"); 
11
+#define p2 do_pause(id, 1, 2, "p2"); 
12
+#define p3 do_pause(id, 1, 3, "p3"); 
13
+#define p4 do_pause(id, 1, 4, "p4"); 
14
+#define p5 do_pause(id, 1, 5, "p5"); 
15
+#define p6 do_pause(id, 1, 6, "p6"); 
16
+
17
+#define c0 do_pause(id, 0, 0, "c0"); 
18
+#define c1 do_pause(id, 0, 1, "c1"); 
19
+#define c2 do_pause(id, 0, 2, "c2"); 
20
+#define c3 do_pause(id, 0, 3, "c3"); 
21
+#define c4 do_pause(id, 0, 4, "c4"); 
22
+#define c5 do_pause(id, 0, 5, "c5"); 
23
+#define c6 do_pause(id, 0, 6, "c6"); 
24
+
25
+int producer_pause_times[MAX_THREADS][7];
26
+int consumer_pause_times[MAX_THREADS][7];
27
+
28
+// needed to avoid interleaving of print out from threads
29
+pthread_mutex_t print_lock = PTHREAD_MUTEX_INITIALIZER;
30
+
31
+void do_print_headers() {
32
+    if (do_trace == 0) 
33
+	return;
34
+    int i;
35
+    printf("%3s ", "NF");
36
+    for (i = 0; i < max; i++) {
37
+	printf(" %3s ", "   ");
38
+    }
39
+    printf("  ");
40
+
41
+    for (i = 0; i < producers; i++) 
42
+	printf("P%d ", i);
43
+    for (i = 0; i < consumers; i++) 
44
+	printf("C%d ", i);
45
+    printf("\n");
46
+}
47
+
48
+void do_print_pointers(int index) {
49
+    if (use_ptr == index && fill_ptr == index) {
50
+	printf("*");
51
+    } else if (use_ptr == index) {
52
+	printf("u");
53
+    } else if (fill_ptr == index) {
54
+	printf("f");
55
+    } else {
56
+	printf(" ");
57
+    }
58
+}
59
+
60
+void do_print_buffer() {
61
+    int i;
62
+    printf("%3d [", num_full);
63
+    for (i = 0; i < max; i++) {
64
+	do_print_pointers(i);
65
+	if (buffer[i] == EMPTY) {
66
+	    printf("%3s ", "---");
67
+	} else if (buffer[i] == END_OF_STREAM) {
68
+	    printf("%3s ", "EOS");
69
+	} else {
70
+	    printf("%3d ", buffer[i]);
71
+	}
72
+    }
73
+    printf("] ");
74
+}
75
+
76
+void do_eos() {
77
+    if (do_trace) {
78
+	Mutex_lock(&print_lock);
79
+	do_print_buffer();
80
+	//printf("%3d [added end-of-stream marker]\n", num_full);
81
+	printf("[main: added end-of-stream marker]\n");
82
+	Mutex_unlock(&print_lock);
83
+    }
84
+}
85
+
86
+void do_pause(int thread_id, int is_producer, int pause_slot, char *str) {
87
+    int i;
88
+    if (do_trace) {
89
+	Mutex_lock(&print_lock);
90
+	do_print_buffer();
91
+
92
+	// skip over other thread's spots
93
+	for (i = 0; i < thread_id; i++) {
94
+	    printf("   ");
95
+	}
96
+	printf("%s\n", str);
97
+	Mutex_unlock(&print_lock);
98
+    }
99
+
100
+    int local_id = thread_id;
101
+    int pause_time;
102
+    if (is_producer) {
103
+	pause_time = producer_pause_times[local_id][pause_slot];
104
+    } else {
105
+	local_id = thread_id - producers;
106
+	pause_time = consumer_pause_times[local_id][pause_slot];
107
+    }
108
+    // printf(" PAUSE %d\n", pause_time);
109
+    sleep(pause_time);
110
+}
111
+
112
+void ensure(int expression, char *msg) {
113
+    if (expression == 0) {
114
+	fprintf(stderr, "%s\n", msg);
115
+	exit(1);
116
+    }
117
+}
118
+
119
+void parse_pause_string(char *str, char *name, int expected_pieces, 
120
+			int pause_array[MAX_THREADS][7]) {
121
+
122
+    // string looks like this (or should):
123
+    //   1,2,0:2,3,4,5
124
+    //   n-1 colons if there are n producers/consumers
125
+    //   comma-separated for sleep amounts per producer or consumer
126
+    int index = 0;
127
+
128
+    char *copy_entire = strdup(str);
129
+    char *outer_marker;
130
+    int colon_count = 0;
131
+    char *p = strtok_r(copy_entire, ":", &outer_marker);
132
+    while (p) {
133
+	// init array: default sleep is 0
134
+	int i;
135
+	for (i = 0; i < 7; i++)  
136
+	    pause_array[index][i] = 0;
137
+
138
+	// for each piece, comma separated
139
+	char *inner_marker;
140
+	char *copy_piece = strdup(p);
141
+	char *c = strtok_r(copy_piece, ",", &inner_marker);
142
+	int comma_count = 0;
143
+
144
+	int inner_index = 0;
145
+	while (c) {
146
+	    int pause_amount = atoi(c);
147
+	    ensure(inner_index < 7, "you specified a sleep string incorrectly... (too many comma-separated args)");
148
+	    // printf("setting %s pause %d to %d\n", name, inner_index, pause_amount);
149
+	    pause_array[index][inner_index] = pause_amount;
150
+	    inner_index++;
151
+
152
+	    c = strtok_r(NULL, ",", &inner_marker);	
153
+	    comma_count++;
154
+	}
155
+	free(copy_piece);
156
+	index++;
157
+
158
+	// continue with colon separated list
159
+	p = strtok_r(NULL, ":", &outer_marker);
160
+	colon_count++;
161
+    }
162
+
163
+    free(copy_entire);
164
+    if (expected_pieces != colon_count) {
165
+	fprintf(stderr, "Error: expected %d %s in sleep specification, got %d\n", expected_pieces, name, colon_count);
166
+	exit(1);
167
+    }
168
+}
169
+
170
+
171
+#endif // __main_header_h__

+ 79
- 0
hw8/simu2/main-one-cv-while.c Vedi File

@@ -0,0 +1,79 @@
1
+#include <ctype.h>
2
+#include <stdio.h>
3
+#include <stdlib.h>
4
+#include <unistd.h>
5
+#include <assert.h>
6
+#include <pthread.h>
7
+#include <sys/time.h>
8
+#include <string.h>
9
+
10
+#include "mythreads.h"
11
+
12
+#include "pc-header.h"
13
+
14
+pthread_cond_t cv = PTHREAD_COND_INITIALIZER;
15
+pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
16
+
17
+#include "main-header.h"
18
+
19
+void do_fill(int value) {
20
+    // ensure empty before usage
21
+    ensure(buffer[fill_ptr] == EMPTY, "error: tried to fill a non-empty buffer");
22
+    buffer[fill_ptr] = value;
23
+    fill_ptr = (fill_ptr + 1) % max;
24
+    num_full++;
25
+}
26
+
27
+int do_get() {
28
+    int tmp = buffer[use_ptr];
29
+    ensure(tmp != EMPTY, "error: tried to get an empty buffer");
30
+    buffer[use_ptr] = EMPTY; 
31
+    use_ptr = (use_ptr + 1) % max;
32
+    num_full--;
33
+    return tmp;
34
+}
35
+
36
+void *producer(void *arg) {
37
+    int id = (int) arg;
38
+    // make sure each producer produces unique values
39
+    int base = id * loops; 
40
+    int i;
41
+    for (i = 0; i < loops; i++) {   p0;
42
+	Mutex_lock(&m);             p1;
43
+	while (num_full == max) {   p2;
44
+	    Cond_wait(&cv, &m);     p3;
45
+	}
46
+	do_fill(base + i);          p4;
47
+	Cond_signal(&cv);           p5;
48
+	Mutex_unlock(&m);           p6;
49
+    }
50
+    return NULL;
51
+}
52
+                                                                               
53
+void *consumer(void *arg) {
54
+    int id = (int) arg;
55
+    int tmp = 0;
56
+    int consumed_count = 0;
57
+    while (tmp != END_OF_STREAM) { c0;
58
+	Mutex_lock(&m);            c1;
59
+	while (num_full == 0) {    c2;
60
+	    Cond_wait(&cv, &m);    c3;
61
+        }
62
+	tmp = do_get();            c4;
63
+	Cond_signal(&cv);          c5;
64
+	Mutex_unlock(&m);          c6;
65
+	consumed_count++;
66
+    }
67
+
68
+    // return consumer_count-1 because END_OF_STREAM does not count
69
+    return (void *) (long long) (consumed_count - 1);
70
+}
71
+
72
+// must set these appropriately to use "main-common.c"
73
+pthread_cond_t *fill_cv = &cv;
74
+pthread_cond_t *empty_cv = &cv;
75
+
76
+// all codes use this common base to start producers/consumers
77
+// and all the other related stuff
78
+#include "main-common.c"
79
+

+ 80
- 0
hw8/simu2/main-two-cvs-if.c Vedi File

@@ -0,0 +1,80 @@
1
+#include <ctype.h>
2
+#include <stdio.h>
3
+#include <stdlib.h>
4
+#include <unistd.h>
5
+#include <assert.h>
6
+#include <pthread.h>
7
+#include <sys/time.h>
8
+#include <string.h>
9
+
10
+#include "mythreads.h"
11
+#include "pc-header.h"
12
+
13
+pthread_cond_t empty  = PTHREAD_COND_INITIALIZER;
14
+pthread_cond_t fill   = PTHREAD_COND_INITIALIZER;
15
+pthread_mutex_t m     = PTHREAD_MUTEX_INITIALIZER;
16
+
17
+#include "main-header.h"
18
+
19
+void do_fill(int value) {
20
+    // ensure empty before usage
21
+    ensure(buffer[fill_ptr] == EMPTY, "error: tried to fill a non-empty buffer");
22
+    buffer[fill_ptr] = value;
23
+    fill_ptr = (fill_ptr + 1) % max;
24
+    num_full++;
25
+}
26
+
27
+int do_get() {
28
+    int tmp = buffer[use_ptr];
29
+    ensure(tmp != EMPTY, "error: tried to get an empty buffer");
30
+    buffer[use_ptr] = EMPTY; 
31
+    use_ptr = (use_ptr + 1) % max;
32
+    num_full--;
33
+    return tmp;
34
+}
35
+
36
+void *producer(void *arg) {
37
+    int id = (int) arg;
38
+    // make sure each producer produces unique values
39
+    int base = id * loops; 
40
+    int i;
41
+    for (i = 0; i < loops; i++) {   p0;
42
+	Mutex_lock(&m);             p1;
43
+	if (num_full == max) {      p2;
44
+	    Cond_wait(&empty, &m);  p3;
45
+	}
46
+	do_fill(base + i);          p4;
47
+	Cond_signal(&fill);         p5;
48
+	Mutex_unlock(&m);           p6;
49
+    }
50
+    return NULL;
51
+}
52
+                                                                               
53
+void *consumer(void *arg) {
54
+    int id = (int) arg;
55
+    int tmp = 0;
56
+    int consumed_count = 0;
57
+    while (tmp != END_OF_STREAM) { c0;
58
+	Mutex_lock(&m);            c1;
59
+	if (num_full == 0) {       c2;
60
+	    Cond_wait(&fill, &m);  c3;
61
+        }
62
+	tmp = do_get();            c4;
63
+	Cond_signal(&empty);       c5;
64
+	Mutex_unlock(&m);          c6;
65
+	consumed_count++;
66
+    }
67
+
68
+    // return consumer_count-1 because END_OF_STREAM does not count
69
+    return (void *) (long long) (consumed_count - 1);
70
+}
71
+
72
+// must set these appropriately to use "main-common.c"
73
+pthread_cond_t *fill_cv = &fill;
74
+pthread_cond_t *empty_cv = &empty;
75
+
76
+// all codes use this common base to start producers/consumers
77
+// and all the other related stuff
78
+#include "main-common.c"
79
+
80
+

+ 84
- 0
hw8/simu2/main-two-cvs-while-extra-unlock.c Vedi File

@@ -0,0 +1,84 @@
1
+#include <ctype.h>
2
+#include <stdio.h>
3
+#include <stdlib.h>
4
+#include <unistd.h>
5
+#include <assert.h>
6
+#include <pthread.h>
7
+#include <sys/time.h>
8
+#include <string.h>
9
+
10
+#include "mythreads.h"
11
+
12
+#include "pc-header.h"
13
+
14
+pthread_cond_t empty  = PTHREAD_COND_INITIALIZER;
15
+pthread_cond_t fill   = PTHREAD_COND_INITIALIZER;
16
+pthread_mutex_t m     = PTHREAD_MUTEX_INITIALIZER;
17
+
18
+#include "main-header.h"
19
+
20
+void do_fill(int value) {
21
+    // ensure empty before usage
22
+    ensure(buffer[fill_ptr] == EMPTY, "error: tried to fill a non-empty buffer");
23
+    buffer[fill_ptr] = value;
24
+    fill_ptr = (fill_ptr + 1) % max;
25
+    num_full++;
26
+}
27
+
28
+int do_get() {
29
+    int tmp = buffer[use_ptr];
30
+    ensure(tmp != EMPTY, "error: tried to get an empty buffer");
31
+    buffer[use_ptr] = EMPTY; 
32
+    use_ptr = (use_ptr + 1) % max;
33
+    num_full--;
34
+    return tmp;
35
+}
36
+
37
+void *producer(void *arg) {
38
+    int id = (int) arg;
39
+    // make sure each producer produces unique values
40
+    int base = id * loops; 
41
+    int i;
42
+    for (i = 0; i < loops; i++) {   p0;
43
+	Mutex_lock(&m);             p1;
44
+	while (num_full == max) {   p2;
45
+	    Cond_wait(&empty, &m);  p3;
46
+	}
47
+	Mutex_unlock(&m);
48
+	do_fill(base + i);          p4;
49
+	Mutex_lock(&m);
50
+	Cond_signal(&fill);         p5;
51
+	Mutex_unlock(&m);           p6;
52
+    }
53
+    return NULL;
54
+}
55
+                                                                               
56
+void *consumer(void *arg) {
57
+    int id = (int) arg;
58
+    int tmp = 0;
59
+    int consumed_count = 0;
60
+    while (tmp != END_OF_STREAM) { c0;
61
+	Mutex_lock(&m);            c1;
62
+	while (num_full == 0) {    c2;
63
+	    Cond_wait(&fill, &m);  c3;
64
+        }
65
+	Mutex_unlock(&m);
66
+	tmp = do_get();            c4;
67
+	Mutex_lock(&m);
68
+	Cond_signal(&empty);       c5;
69
+	Mutex_unlock(&m);          c6;
70
+	consumed_count++;
71
+    }
72
+
73
+    // return consumer_count-1 because END_OF_STREAM does not count
74
+    return (void *) (long long) (consumed_count - 1);
75
+}
76
+
77
+// must set these appropriately to use "main-common.c"
78
+pthread_cond_t *fill_cv = &fill;
79
+pthread_cond_t *empty_cv = &empty;
80
+
81
+// all codes use this common base to start producers/consumers
82
+// and all the other related stuff
83
+#include "main-common.c"
84
+

+ 83
- 0
hw8/simu2/main-two-cvs-while.c Vedi File

@@ -0,0 +1,83 @@
1
+#include <ctype.h>
2
+#include <stdio.h>
3
+#include <stdlib.h>
4
+#include <unistd.h>
5
+#include <assert.h>
6
+#include <pthread.h>
7
+#include <sys/time.h>
8
+#include <string.h>
9
+
10
+#include "mythreads.h"
11
+
12
+#include "pc-header.h"
13
+
14
+// used in producer/consumer signaling protocol
15
+pthread_cond_t empty  = PTHREAD_COND_INITIALIZER;
16
+pthread_cond_t fill   = PTHREAD_COND_INITIALIZER;
17
+pthread_mutex_t m     = PTHREAD_MUTEX_INITIALIZER;
18
+
19
+#include "main-header.h"
20
+
21
+void do_fill(int value) {
22
+    // ensure empty before usage
23
+    ensure(buffer[fill_ptr] == EMPTY, "error: tried to fill a non-empty buffer");
24
+    buffer[fill_ptr] = value;
25
+    fill_ptr = (fill_ptr + 1) % max;
26
+    num_full++;
27
+}
28
+
29
+int do_get() {
30
+    int tmp = buffer[use_ptr];
31
+    ensure(tmp != EMPTY, "error: tried to get an empty buffer");
32
+    buffer[use_ptr] = EMPTY; 
33
+    use_ptr = (use_ptr + 1) % max;
34
+    num_full--;
35
+    return tmp;
36
+}
37
+
38
+void *producer(void *arg) {
39
+    int id = (int) arg;
40
+    // make sure each producer produces unique values
41
+    int base = id * loops; 
42
+    int i;
43
+    for (i = 0; i < loops; i++) {   p0;
44
+	Mutex_lock(&m);             p1;
45
+	while (num_full == max) {   p2;
46
+	    Cond_wait(&empty, &m);  p3;
47
+	}
48
+	do_fill(base + i);          p4;
49
+	Cond_signal(&fill);         p5;
50
+	Mutex_unlock(&m);           p6;
51
+    }
52
+    return NULL;
53
+}
54
+                                                                               
55
+void *consumer(void *arg) {
56
+    int id = (int) arg;
57
+    int tmp = 0;
58
+    int consumed_count = 0;
59
+    while (tmp != END_OF_STREAM) { c0;
60
+	Mutex_lock(&m);            c1;
61
+	while (num_full == 0) {    c2;
62
+	    Cond_wait(&fill, &m);  c3;
63
+        }
64
+	tmp = do_get();            c4;
65
+	Cond_signal(&empty);       c5;
66
+	Mutex_unlock(&m);          c6;
67
+	consumed_count++;
68
+    }
69
+
70
+    // return consumer_count-1 because END_OF_STREAM does not count
71
+    return (void *) (long long) (consumed_count - 1);
72
+}
73
+
74
+// must set these appropriately to use "main-common.c"
75
+pthread_cond_t *fill_cv = &fill;
76
+pthread_cond_t *empty_cv = &empty;
77
+
78
+// all codes use this common base to start producers/consumers
79
+// and all the other related stuff
80
+#include "main-common.c"
81
+
82
+
83
+

+ 69
- 0
hw8/simu2/mythreads.h Vedi File

@@ -0,0 +1,69 @@
1
+#ifndef __MYTHREADS_h__
2
+#define __MYTHREADS_h__
3
+
4
+#include <pthread.h>
5
+#include <assert.h>
6
+#include <sys/time.h>
7
+#include <stdlib.h>
8
+
9
+void *Malloc(size_t size) {
10
+    void *p = malloc(size);
11
+    assert(p != NULL);
12
+    return p;
13
+}
14
+
15
+double Time_GetSeconds() {
16
+    struct timeval t;
17
+    int rc = gettimeofday(&t, NULL);
18
+    assert(rc == 0);
19
+    return (double) ((double)t.tv_sec + (double)t.tv_usec / 1e6);
20
+}
21
+
22
+void work(int seconds) {
23
+    double t0 = Time_GetSeconds();
24
+    while ((Time_GetSeconds() - t0) < (double)seconds)
25
+	;
26
+}
27
+
28
+void Mutex_init(pthread_mutex_t *m) {
29
+    assert(pthread_mutex_init(m, NULL) == 0);
30
+}
31
+
32
+void Mutex_lock(pthread_mutex_t *m) {
33
+    int rc = pthread_mutex_lock(m);
34
+    assert(rc == 0);
35
+}
36
+
37
+void Mutex_unlock(pthread_mutex_t *m) {
38
+    int rc = pthread_mutex_unlock(m);
39
+    assert(rc == 0);
40
+}
41
+
42
+void Cond_init(pthread_cond_t *c) {
43
+    assert(pthread_cond_init(c, NULL) == 0);
44
+}
45
+
46
+void Cond_wait(pthread_cond_t *c, pthread_mutex_t *m) {
47
+    int rc = pthread_cond_wait(c, m);
48
+    assert(rc == 0);
49
+}
50
+
51
+void Cond_signal(pthread_cond_t *c) {
52
+    int rc = pthread_cond_signal(c);
53
+    assert(rc == 0);
54
+}
55
+
56
+
57
+void Pthread_create(pthread_t *thread, const pthread_attr_t *attr, 	
58
+		    void *(*start_routine)(void*), void *arg) {
59
+    int rc = pthread_create(thread, attr, start_routine, arg);
60
+    assert(rc == 0);
61
+}
62
+
63
+void Pthread_join(pthread_t thread, void **value_ptr) {
64
+    int rc = pthread_join(thread, value_ptr);
65
+    assert(rc == 0);
66
+}
67
+
68
+
69
+#endif // __MYTHREADS_h__

+ 22
- 0
hw8/simu2/pc-header.h Vedi File

@@ -0,0 +1,22 @@
1
+#ifndef __pc_header_h__
2
+#define __pc_header_h__
3
+
4
+#define MAX_THREADS (100)  // maximum number of producers/consumers
5
+
6
+int producers = 1;         // number of producers
7
+int consumers = 1;         // number of consumers
8
+
9
+int *buffer;               // the buffer itself: malloc in main()
10
+int max;                   // size of the producer/consumer buffer
11
+
12
+int use_ptr  = 0;          // tracks where next consume should come from
13
+int fill_ptr = 0;          // tracks where next produce should go to
14
+int num_full = 0;          // counts how many entries are full
15
+
16
+int loops;                 // number of items that each producer produces
17
+
18
+#define EMPTY         (-2) // buffer slot has nothing in it
19
+#define END_OF_STREAM (-1) // consumer who grabs this should exit
20
+
21
+#endif // __pc_header_h__
22
+ 

+ 258
- 0
hw8/task1/README.md Vedi File

@@ -0,0 +1,258 @@
1
+# Homework hw8 task1
2
+
3
+-   ['Proof-of-Work' als Bibliothek](#proof-of-work-als-bibliothek)
4
+    -   [Idiomatischer Code](#idiomatischer-code)
5
+    -   [`ANSWERS.md`](#answersmd)
6
+-   [Implementierung](#implementierung)
7
+    -   [Parameter _threads_](#parameter-threads)
8
+    -   [Flag _verbose_](#flag-verbose)
9
+    -   [Suchen mit mehreren Threads](#suchen-mit-mehreren-threads)
10
+        -   [-s Flag](#s-flag)
11
+        -   [-w Flag](#w-flag)
12
+        -   [Weitere Options](#weitere-options)
13
+        -   [-help Ausgabe](#help-ausgabe)
14
+    -   [Command timings](#command-timings)
15
+    -   [Tests](#tests)
16
+    -   [Laufzeit Messungen (im --release Mode)](#laufzeit-messungen-im---release-mode)
17
+
18
+## 'Proof-of-Work' als Bibliothek
19
+
20
+Transformieren Sie Ihr Binary Projekt in ein Library Projekt, wobei zusätzlich
21
+ein CLI Programm bereit gestellt wird. Dieses hat seinen Ursprung in `main.rs`.
22
+Nach der Transformation soll **cargo run** die Funktionalität Ihres hw7/task1
23
+Programms aufzeigen, und **cargo test** durchläuft die Unit Tests der Library.
24
+Erstellen Sie auf Basis der bisherigen "unit_test" Datei eine `tests/task1.rs`
25
+Datei, die die pub Funktionen der Library testet.
26
+
27
+### Idiomatischer Code
28
+
29
+In dieser Aufgabe sollte Sie besonderen Wert auf Idiomatischen Rust Code legen.
30
+Auch sollte Ihre `main.rs` Funktion möglichst übersichtlich bleiben. Lagern Sie
31
+somit Funktionen in Module Ihrer Library oder für Ihr Binary aus. In den
32
+vorherigen Homeworks haben Sie dazu alle nötigen Kniffe kennen gelernt, die Sie
33
+nun in Ihrer Lösung verwenden.
34
+
35
+### `ANSWERS.md`
36
+
37
+In den Aufgaben werden immer wieder Fragen gestellt. Geben Sie die Antworten
38
+dazu in der Datei `ANSWERS.md`. Eine kurze aber nachvollziehbare Erklärung (eigene
39
+Worte, kein Code) ist ausreichend. Aufbau und Struktur Ihrer `ANSWERS.md` ist
40
+Ihnen überlassen. Überzeugen Sie uns Tutoren mit einer klaren und verständlichen
41
+Struktur (und korrekter Formatierung). 
42
+
43
+Kommentieren Sie Ihren Code. Beschreiben Sie in eigenen Worten (kein Copy/Paste
44
+von Code oder Code Kommentaren) in `ANSWERS.md`, welche Arten der
45
+Synchronisation und Kommunikation (ausser MPSC) Sie einsetzen und wie Sie die
46
+maximale Performance erreichen. Wichtig: Kommentare gehören in den Code,
47
+Erklärungen in die `ANSWERS.md`.
48
+
49
+Ihre Erklärung muss nachvollziehbar sein, ohne weitere eigenen Recherchen. Zum
50
+Beispiel die Antwort: "In der Implementierung wird die Systemfunktion
51
+_dummy_bla_blub()_ benutzt" ist NICHT ausreichend. Ausreichend wäre: "In der
52
+Implementierung wird die Systemfunktion _dummy_bla_blub()_ benutzt, die die
53
+Uhrzeit seit Einschalten des Rechners als UTF String liefert."
54
+
55
+Messungen müssen auf Ihrem Container durchgeführt werden.
56
+
57
+Und noch einmal: Achten Sie auf eine korrekte Formatierung in Markdown!
58
+
59
+## Implementierung
60
+
61
+### Parameter _threads_
62
+
63
+Erweitern Sie das CLI Programm mit einem weiteren **OPTIONALEN** Parameter:
64
+`threads`. Wird der Parameter nicht angegeben, analysiert das CLI Programm das
65
+System und ermittelt die Anzahl der vorhandenen CPUs für 'threads'. Geben Sie
66
+den Parameter an, können Sie diesen Wert somit 'überschreiben'.
67
+
68
+Benutzen Sie dafür die externe [Crate sys-info](<>). Was ist der Unterschied
69
+zwischen einer logischen und einer physikalischen CPU?
70
+
71
+```text
72
+Proof of Work Mechanism 0.2
73
+Satoshi Nakamoto
74
+now the basics in Rust, no C anymore
75
+
76
+USAGE:
77
+    task1 [FLAGS] <base> <difficulty> [threads] [SUBCOMMAND]
78
+
79
+FLAGS:
80
+    -h, --help       Prints help information
81
+    -V, --version    Prints version information
82
+
83
+ARGS:
84
+    <base>          Sets the base to use
85
+    <difficulty>    Sets the difficulty to use
86
+    <threads>       Sets the number of threads to use (default = number of cpus)
87
+
88
+SUBCOMMANDS:
89
+    help       Prints this message or the help of the given subcommand(s)
90
+    timings    controls timing features
91
+```
92
+
93
+### Flag _verbose_
94
+
95
+Mit dem Parameter _-v_, _-vv_ oder _-vvv_ usw. lassen sich unterschiedliche
96
+viele Zusatzinformationen ausgeben. 
97
+
98
+Ohne die Angabe von verbose reduzieren Sie bitte die Ausgaben auf:
99
+
100
+    Please wait ...
101
+    Number: 567621 --> hash: b6bea2a40ed1bd6d9999b2232072092f3df0e02c4b507aa3ad947367b9712345
102
+
103
+Wird das Flag -v angegeben, so soll ein Header mit Informationen zum System und
104
+die Anzahl der Threads, die zur Suche benutzt werden, ausgegeben werden:
105
+
106
+```text
107
+--------------------------------------------
108
+Container:    : "ct-bsys-ws17-0"
109
+Physical CPUs : 4
110
+Logical CPUs  : 4
111
+CPU Speed     : 1700
112
+Load Average  : LoadAvg { one: 2.4, five: 2.43, fifteen: 2.46 }
113
+Processes     : 2690
114
+--------------------------------------------
115
+Searching with 2 threads!
116
+Please wait ...
117
+Number: 567621 --> hash: b6bea2a40ed1bd6d9999b2232072092f3df0e02c4b507aa3ad947367b9712345
118
+```
119
+
120
+Benutzen Sie für die Systeminformationen die externe [Crate sys-info](<>).
121
+
122
+### Suchen mit mehreren Threads
123
+
124
+Lagern Sie Ihre bisherige Suche in eine eigene Funktion aus. Schreiben Sie dann
125
+eine neue Funktion, die das Suchen auf mehrere Threads aufteilt, wenn die Anzahl
126
+der Threads >1 ist. 
127
+
128
+Für die Aufteilung der Aufgabe bietet sich eine "Multiple Producer -> Single
129
+Consumer" Lösung an. Viele Producer suchen nach dem Hash. Die zu untersuchende
130
+'Menge' an Zahlen muss dazu geeignet auf alle Threads verteilt werden. Der
131
+Producer, der ihn findet, sendet den Hash an den Consumer.  
132
+
133
+Die Rust Standardbibliothek bietet dafür die [MPSC](<>) Synchronisationsprimitive
134
+an. Einen ähnlichen Mechanismus haben Sie bereits mit der Pipe kennen gelernt. 
135
+
136
+Findet einer der Producer das Ergebnis, so sender er dies dan den Consumer. Der
137
+Consumer hat dann die ruhmreiche Aufgabe, den Hash ausgeben zu dürfen und
138
+beendet dann das Programm. 
139
+
140
+#### -s Flag
141
+
142
+Wird das Flag -s angegeben, so beenden die Producer-Threads die Suche, wenn ein
143
+Ergebnis gefunden wurde. Synchronisieren Sie dazu alle Producer Threads über
144
+eine geeignete Variable (kein Mutex, CV oder Semaphore). Die Producer-Threads
145
+beenden sich somit wenn:
146
+
147
+-   ein Ergebnis gefunden wurde
148
+-   ein anderer Thread ein Ergebnis gefunden hat
149
+
150
+Benutzen Sie die Synchronisation geeignet, um den Overhead durch die
151
+Synchronisation gering zu halten. Ergänzen Sie Ihr Programm z.B. mit einer
152
+weiteren eigenen 'Option' (Name frei wählbar), um Parameter an Ihrer
153
+Synchronisation verändern zu können.
154
+
155
+#### -w Flag
156
+
157
+Wird dieses Flag angegeben, so wartet der Consumer Thread auf das Ende aller
158
+Producer Threads. Das Flag macht natürlich nur Sinn, wenn das -s Flag angegeben
159
+ist. Die Trennung von '-s' und '-w' dient in dieser Aufgabe zum
160
+'Experimentieren'. 
161
+
162
+#### Weitere Options
163
+
164
+Sofern Sie weitere Optionen beim Aufruf Ihres Programms benötigen, ergänzen Sie
165
+diese bitte mit einer knappen aber aussagekräftigen Hilfe beim Aufruf mit
166
+`--help`. Nutzen Sie die `verbose` Ausgaben ('-vv' und '-vvv'), um den Ablauf
167
+des Programms (und der Threads) zu 'loggen'. Das macht es für uns Tutoren als
168
+auch für Sie einfacher, den Ablauf Ihres Programms zu 'debuggen'.
169
+
170
+#### -help Ausgabe
171
+
172
+Mit den obigen Flags und Options sollten Sie nun folgende --help Ausgabe
173
+erhalten:
174
+
175
+```text
176
+USAGE:
177
+    task1 [FLAGS] [OPTIONS] <base> <difficulty> [threads] [SUBCOMMAND]
178
+
179
+FLAGS:
180
+    -h, --help       Prints help information
181
+    -s, --sync       enables sync when solution found
182
+    -v               Sets the level of verbosity
183
+    -V, --version    Prints version information
184
+    -w, --wait       consumer waits for all producers
185
+
186
+OPTIONS:
187
+        --special <VALUE>    sets special sync parameter (Default 0)
188
+
189
+ARGS:
190
+    <base>          Sets the base to use
191
+    <difficulty>    Sets the difficulty to use
192
+    <threads>       Sets the number of threads to use (default = number of cpus)
193
+
194
+SUBCOMMANDS:
195
+    help       Prints this message or the help of the given subcommand(s)
196
+    timings    controls timing features
197
+```
198
+
199
+### Command timings
200
+
201
+Bei Angabe des Kommandos 'timings' wird die Gesamtzeit der Suche wie bisher
202
+ausgegeben. Es genügt somit, bei der Standardausgabe (ohne -v) die Zeit der
203
+Suche im Consumer Thread zu messen.
204
+
205
+```text
206
+...
207
+Please wait ...
208
+Number: 567621 --> hash: b6bea2a40ed1bd6d9999b2232072092f3df0e02c4b507aa3ad947367b9712345
209
+(Duration: 2s / 2684ms / 2684903us)
210
+```
211
+
212
+Wird das Flag '-v' mit angegeben, so summiert der Consumer Thread die einzelnen
213
+Suchzeiten der Producer Threads, sowie die insgesamt durchgeführten Loops zur
214
+Suche. Überlegen Sie sich dazu, wie Sie die nötigen Informationen im Consumer
215
+von den Producern erfahren. Eine Ausgabe der Ergebnis könnte folgendermassen
216
+aussehen:
217
+
218
+```text
219
+...
220
+Sum Loops in Producers:       567600
221
+Sum Duration in Producers:    10s / 10684ms / 10684637us
222
+```
223
+
224
+Diskutieren Sie die sich ergebenden Werte in `ANSWERS.md`
225
+
226
+### Tests
227
+
228
+Alle Integrations-Tests der Library liegen unter `tests/`.
229
+
230
+TODO: Bei den bats Tests ist ein "timeout"-Test dabei. Dieser schlägt zu, wenn
231
+Sie kein korrektes Muster als Parameter angeben. Der Test benutzt das Programm
232
+'timeout', welches auf Linux Systemen installiert ist.
233
+
234
+### Laufzeit Messungen (im --release Mode)
235
+
236
+(Diskussion Ihrer Ergebnisse in `ANSWERS.md`)
237
+
238
+Zeigen Sie Quantitativ den Performance-Gewinn gegenüber der Single-Threaded
239
+Suche anhand einzelner Ergebnisse. Welche (ungefähre) Abhängigkeit der Laufzeit
240
+von der Anzahl der Threads können Sie im --release Mode bestimmen? 
241
+
242
+Nutzen Sie das Unix Tool **time**, um zusätzliche Ausgaben über die Laufzeit
243
+Ihres Programms zu erhalten (Antworten in die Datei `ANSWERS.md`). Von Interesse
244
+sind nur Ihre --release Zeiten:
245
+
246
+-   Wie ist die Ausgabe von **time** zu interpretieren, wenn mehrere Threads
247
+    laufen?
248
+-   Welche unterschiedlichen Ergebnisse erhalten Sie bei der Option timings? Wie
249
+    stehen diese im Zusammenhang mit den Ergebnissen von **time**.
250
+-   Welche Quantitative Auswirkungen hat die Synchronisierung (Warum)? 
251
+-   Wie verhält sich die Performance, wenn die von Ihnen zusätzlichen Parameter
252
+    (falls vorhanden) zur Optimierung der Performance variiert werden?
253
+
254
+[Crate clap]: https://docs.rs/clap/
255
+[Crate sha2]: https://docs.rs/sha2/
256
+[Crate time]: https://docs.rs/time/
257
+[Crate sys-info]: https://docs.rs/sys-info/
258
+[MPSC]: https://doc.rust-lang.org/std/sync/mpsc/

Loading…
Annulla
Salva