Difference between revisions of "Sezioni Critiche in C"
Jump to navigation
Jump to search
(Created page with "Tutti questi esempi hanno NPROC thread che svolgono un loop MAX volte sommando 1 ad ogni iterazione ad una variabile globale condivisa var; Se tutto va bene il risultato fina...") |
m (→Dekker) |
||
(2 intermediate revisions by 2 users not shown) | |||
Line 1: | Line 1: | ||
− | Tutti questi esempi hanno NPROC thread che svolgono un loop MAX volte sommando 1 ad ogni iterazione ad una variabile globale condivisa | + | Tutti questi esempi hanno NPROC thread che svolgono un loop MAX volte sommando 1 ad ogni iterazione ad una variabile globale condivisa val. |
Se tutto va bene il risultato finale deve essere NPROC*MAX. | Se tutto va bene il risultato finale deve essere NPROC*MAX. | ||
Line 28: | Line 28: | ||
/* enter */ | /* enter */ | ||
need[id] = 1; | need[id] = 1; | ||
+ | __sync_synchronize(); | ||
while (need[1 - id]) { | while (need[1 - id]) { | ||
+ | __sync_synchronize(); | ||
if (turn != id) { | if (turn != id) { | ||
need[id] = 0; | need[id] = 0; | ||
Line 35: | Line 37: | ||
need[id] = 1; | need[id] = 1; | ||
} | } | ||
+ | __sync_synchronize(); | ||
} | } | ||
/* /enter */ | /* /enter */ | ||
Line 53: | Line 56: | ||
pthread_t t[NPROC]; | pthread_t t[NPROC]; | ||
srand(time(NULL)); | srand(time(NULL)); | ||
+ | for (i=0; i<NPROC; i++) | ||
+ | pthread_create(&t[i], NULL, codet, (void *) i); | ||
+ | for (i=0; i<NPROC; i++) | ||
+ | pthread_join(t[i], NULL); | ||
+ | printf("%ld %d\n",val,NPROC*MAX); | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | == Peterson == | ||
+ | Proviamo con Peterson | ||
+ | <syntaxhighlight lang=C> | ||
+ | #include<stdio.h> | ||
+ | #include<stdint.h> | ||
+ | #include<pthread.h> | ||
+ | #include<unistd.h> | ||
+ | |||
+ | #define MAX 4000000 | ||
+ | #define STEP 1000000 | ||
+ | #define NPROC 2 | ||
+ | |||
+ | volatile long val = 0; | ||
+ | volatile long need[NPROC]; | ||
+ | volatile long turn; | ||
+ | |||
+ | void *codet(void *arg) { | ||
+ | uintptr_t id = (uintptr_t) arg; | ||
+ | int count; | ||
+ | for (count=0; count < MAX; count++) { | ||
+ | /* enter */ | ||
+ | need[id] = 1; | ||
+ | turn = 1 - id; | ||
+ | __sync_synchronize(); | ||
+ | while (need[1 - id] && turn != id) | ||
+ | ; | ||
+ | /* /enter */ | ||
+ | val = val + 1; | ||
+ | if (count % STEP == 0) | ||
+ | printf("%d %ld\n",id,val); | ||
+ | /* exit */ | ||
+ | need[id] = 0; | ||
+ | /* /exit */ | ||
+ | } | ||
+ | } | ||
+ | |||
+ | int main(int argc, char *argv[]) { | ||
+ | uintptr_t i; | ||
+ | pthread_t t[NPROC]; | ||
for (i=0; i<NPROC; i++) | for (i=0; i<NPROC; i++) | ||
pthread_create(&t[i], NULL, codet, (void *) i); | pthread_create(&t[i], NULL, codet, (void *) i); | ||
Line 60: | Line 110: | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
+ | La funzione del compilatore C "__sync_synchronize" (che nella macchine Intel genera l'istuzione assembler "__asm__ __volatile__("mfence");") è necessaria per x86 multicore perché altrimenti le cache dei processori potrebbero non essere allineate. | ||
+ | |||
+ | == Peterson per N processi == | ||
+ | <syntaxhighlight lang=C> | ||
+ | #include<stdio.h> | ||
+ | #include<stdint.h> | ||
+ | #include<pthread.h> | ||
+ | #include<unistd.h> | ||
+ | |||
+ | #define MAX 4000000 | ||
+ | #define NPROC 4 | ||
+ | |||
+ | volatile long val = 0; | ||
+ | volatile long turn; | ||
+ | volatile long stage[NPROC]; | ||
+ | volatile long last[NPROC]; | ||
+ | |||
+ | void *codet(void *arg) { | ||
+ | uintptr_t id = (uintptr_t) arg; | ||
+ | int count; | ||
+ | int j, k; | ||
+ | for (count=0; count < MAX; count++) { | ||
+ | /* enter */ | ||
+ | for (j=0; j<NPROC; j++) { | ||
+ | stage[id] = j+1; last[j] = id; | ||
+ | __sync_synchronize(); | ||
+ | for (k=0; k<NPROC; k++) { | ||
+ | if (id != k) | ||
+ | while (stage[k] >= stage[id] && last[j] == id) | ||
+ | ; | ||
+ | } | ||
+ | } | ||
+ | /* /enter */ | ||
+ | val = val + 1; | ||
+ | if ((count & ((1<<20) - 1)) == 0) | ||
+ | printf("%d %ld\n",id,val); | ||
+ | /* exit */ | ||
+ | stage[id] = 0; | ||
+ | /* /exit */ | ||
+ | } | ||
+ | } | ||
+ | |||
+ | int main(int argc, char *argv[]) { | ||
+ | uintptr_t i; | ||
+ | pthread_t t[NPROC]; | ||
+ | for (i=0; i<NPROC; i++) | ||
+ | pthread_create(&t[i], NULL, codet, (void *) i); | ||
+ | for (i=0; i<NPROC; i++) | ||
+ | pthread_join(t[i], NULL); | ||
+ | printf("%d %d\n",val,NPROC*MAX); | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | == Test&Set == | ||
+ | |||
+ | Ora proviamo con test&set (che viene fornito dal compilatore C) | ||
+ | <syntaxhighlight lang=C> | ||
+ | #include<stdio.h> | ||
+ | #include<stdint.h> | ||
+ | #include<pthread.h> | ||
+ | #include<unistd.h> | ||
+ | #include<sched.h> | ||
+ | |||
+ | #define MAX 4000000 | ||
+ | #define STEP 1000000 | ||
+ | #define NPROC 4 | ||
+ | |||
+ | volatile long val = 0; | ||
+ | volatile long lock = 0; | ||
+ | |||
+ | void *codet(void *arg) { | ||
+ | uintptr_t id = (uintptr_t) arg; | ||
+ | int count; | ||
+ | for (count=0; count < MAX; count++) { | ||
+ | /* enter */ | ||
+ | while (__sync_lock_test_and_set(&lock,1)) | ||
+ | /*sched_yield()*/; | ||
+ | /* /enter */ | ||
+ | val = val + 1; | ||
+ | if (count % STEP == 0) | ||
+ | printf("%d %ld\n",id,val); | ||
+ | /* exit */ | ||
+ | __sync_lock_release(&lock); | ||
+ | /* /exit */ | ||
+ | } | ||
+ | } | ||
+ | |||
+ | int main(int argc, char *argv[]) { | ||
+ | uintptr_t i; | ||
+ | pthread_t t[NPROC]; | ||
+ | for (i=0; i<NPROC; i++) | ||
+ | pthread_create(&t[i], NULL, codet, (void *) i); | ||
+ | for (i=0; i<NPROC; i++) | ||
+ | pthread_join(t[i], NULL); | ||
+ | printf("%d %d\n",val,NPROC*MAX); | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | questo funziona bene! | ||
+ | (se si decommenta la chiamata (di system call) sched_yield() l'esecuzione è molto più veloce, perché?) | ||
+ | |||
+ | == Test&Set fra processi == | ||
+ | |||
+ | Ecco il T&S: | ||
+ | <syntaxhighlight lang=C> | ||
+ | #include<stdio.h> | ||
+ | #include<fcntl.h> | ||
+ | #include<stdlib.h> | ||
+ | #include<unistd.h> | ||
+ | #include<sched.h> | ||
+ | #include <sys/mman.h> | ||
+ | |||
+ | #define MAX 400000000 | ||
+ | #define STEP 1000000 | ||
+ | |||
+ | volatile int *shared; | ||
+ | #define val shared[0] | ||
+ | #define lock shared[1] | ||
+ | |||
+ | void *create_shmem(char *name, off_t length) { | ||
+ | int shmfd = shm_open(name, O_CREAT|O_RDWR, 0600); | ||
+ | ftruncate(shmfd,length); | ||
+ | return mmap(NULL, length, PROT_READ|PROT_WRITE, MAP_SHARED, shmfd, 0); | ||
+ | } | ||
+ | |||
+ | int destroy_shmem(char *name) { | ||
+ | return shm_unlink(name); | ||
+ | } | ||
+ | |||
+ | int main(int argc, char *argv[]) { | ||
+ | shared = create_shmem("test", 3*sizeof(int)); | ||
+ | int count; | ||
+ | for (count = 0; count < MAX; count ++) { | ||
+ | /* enter */ | ||
+ | while (__sync_lock_test_and_set(&lock,1)) | ||
+ | sched_yield(); | ||
+ | /* /enter */ | ||
+ | val = val + 1; | ||
+ | if (count % STEP == 0) | ||
+ | printf("%ld\n",val); | ||
+ | /* exit */ | ||
+ | __sync_lock_release(&lock); | ||
+ | /* /exit */ | ||
+ | } | ||
+ | printf("%d\n",val); | ||
+ | |||
+ | destroy_shmem("test"); | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | * shm_create e shm_unlink sono definite nella libreria librt | ||
+ | * lanciate questo programma da piu' terminali virtuali | ||
+ | * se bloccate l'esecuzione puo' rimane definita l'area di memoria condivisa. se quando avete "ucciso" il processo lock valeva 1, il programma non funzionera' piu'. In GNU-Linux le aree di memoria condivisa sono visibili nel file system nella directory "/dev/shm", quindi occorre cancellare lo pseudo-file "/dev/shm/test". |
Latest revision as of 08:24, 17 October 2017
Tutti questi esempi hanno NPROC thread che svolgono un loop MAX volte sommando 1 ad ogni iterazione ad una variabile globale condivisa val.
Se tutto va bene il risultato finale deve essere NPROC*MAX.
Dekker
#include<stdio.h>
#include<stdint.h>
#include<stdlib.h>
#include<sched.h>
#include<pthread.h>
#include<unistd.h>
#define MAX 400
#define STEP 10
#define NPROC 2
volatile long val = 0;
int need[NPROC];
int turn;
void *codet(void *arg) {
uintptr_t id = (uintptr_t) arg;
int count;
long tmp;
for (count=0; count < MAX; count++) {
/* enter */
need[id] = 1;
__sync_synchronize();
while (need[1 - id]) {
__sync_synchronize();
if (turn != id) {
need[id] = 0;
while (turn != id)
sched_yield();
need[id] = 1;
}
__sync_synchronize();
}
/* /enter */
tmp = val;
//usleep(rand() % 2);
val = tmp + 1;
if (count % STEP == 0)
printf("%d %ld\n",id,val);
/* exit */
turn = 1 - id;
need[id] = 0;
/* /exit */
}
}
int main(int argc, char *argv[]) {
uintptr_t i;
pthread_t t[NPROC];
srand(time(NULL));
for (i=0; i<NPROC; i++)
pthread_create(&t[i], NULL, codet, (void *) i);
for (i=0; i<NPROC; i++)
pthread_join(t[i], NULL);
printf("%ld %d\n",val,NPROC*MAX);
}
Peterson
Proviamo con Peterson
#include<stdio.h>
#include<stdint.h>
#include<pthread.h>
#include<unistd.h>
#define MAX 4000000
#define STEP 1000000
#define NPROC 2
volatile long val = 0;
volatile long need[NPROC];
volatile long turn;
void *codet(void *arg) {
uintptr_t id = (uintptr_t) arg;
int count;
for (count=0; count < MAX; count++) {
/* enter */
need[id] = 1;
turn = 1 - id;
__sync_synchronize();
while (need[1 - id] && turn != id)
;
/* /enter */
val = val + 1;
if (count % STEP == 0)
printf("%d %ld\n",id,val);
/* exit */
need[id] = 0;
/* /exit */
}
}
int main(int argc, char *argv[]) {
uintptr_t i;
pthread_t t[NPROC];
for (i=0; i<NPROC; i++)
pthread_create(&t[i], NULL, codet, (void *) i);
for (i=0; i<NPROC; i++)
pthread_join(t[i], NULL);
printf("%d %d\n",val,NPROC*MAX);
}
La funzione del compilatore C "__sync_synchronize" (che nella macchine Intel genera l'istuzione assembler "__asm__ __volatile__("mfence");") è necessaria per x86 multicore perché altrimenti le cache dei processori potrebbero non essere allineate.
Peterson per N processi
#include<stdio.h>
#include<stdint.h>
#include<pthread.h>
#include<unistd.h>
#define MAX 4000000
#define NPROC 4
volatile long val = 0;
volatile long turn;
volatile long stage[NPROC];
volatile long last[NPROC];
void *codet(void *arg) {
uintptr_t id = (uintptr_t) arg;
int count;
int j, k;
for (count=0; count < MAX; count++) {
/* enter */
for (j=0; j<NPROC; j++) {
stage[id] = j+1; last[j] = id;
__sync_synchronize();
for (k=0; k<NPROC; k++) {
if (id != k)
while (stage[k] >= stage[id] && last[j] == id)
;
}
}
/* /enter */
val = val + 1;
if ((count & ((1<<20) - 1)) == 0)
printf("%d %ld\n",id,val);
/* exit */
stage[id] = 0;
/* /exit */
}
}
int main(int argc, char *argv[]) {
uintptr_t i;
pthread_t t[NPROC];
for (i=0; i<NPROC; i++)
pthread_create(&t[i], NULL, codet, (void *) i);
for (i=0; i<NPROC; i++)
pthread_join(t[i], NULL);
printf("%d %d\n",val,NPROC*MAX);
}
Test&Set
Ora proviamo con test&set (che viene fornito dal compilatore C)
#include<stdio.h>
#include<stdint.h>
#include<pthread.h>
#include<unistd.h>
#include<sched.h>
#define MAX 4000000
#define STEP 1000000
#define NPROC 4
volatile long val = 0;
volatile long lock = 0;
void *codet(void *arg) {
uintptr_t id = (uintptr_t) arg;
int count;
for (count=0; count < MAX; count++) {
/* enter */
while (__sync_lock_test_and_set(&lock,1))
/*sched_yield()*/;
/* /enter */
val = val + 1;
if (count % STEP == 0)
printf("%d %ld\n",id,val);
/* exit */
__sync_lock_release(&lock);
/* /exit */
}
}
int main(int argc, char *argv[]) {
uintptr_t i;
pthread_t t[NPROC];
for (i=0; i<NPROC; i++)
pthread_create(&t[i], NULL, codet, (void *) i);
for (i=0; i<NPROC; i++)
pthread_join(t[i], NULL);
printf("%d %d\n",val,NPROC*MAX);
}
questo funziona bene! (se si decommenta la chiamata (di system call) sched_yield() l'esecuzione è molto più veloce, perché?)
Test&Set fra processi
Ecco il T&S:
#include<stdio.h>
#include<fcntl.h>
#include<stdlib.h>
#include<unistd.h>
#include<sched.h>
#include <sys/mman.h>
#define MAX 400000000
#define STEP 1000000
volatile int *shared;
#define val shared[0]
#define lock shared[1]
void *create_shmem(char *name, off_t length) {
int shmfd = shm_open(name, O_CREAT|O_RDWR, 0600);
ftruncate(shmfd,length);
return mmap(NULL, length, PROT_READ|PROT_WRITE, MAP_SHARED, shmfd, 0);
}
int destroy_shmem(char *name) {
return shm_unlink(name);
}
int main(int argc, char *argv[]) {
shared = create_shmem("test", 3*sizeof(int));
int count;
for (count = 0; count < MAX; count ++) {
/* enter */
while (__sync_lock_test_and_set(&lock,1))
sched_yield();
/* /enter */
val = val + 1;
if (count % STEP == 0)
printf("%ld\n",val);
/* exit */
__sync_lock_release(&lock);
/* /exit */
}
printf("%d\n",val);
destroy_shmem("test");
}
- shm_create e shm_unlink sono definite nella libreria librt
- lanciate questo programma da piu' terminali virtuali
- se bloccate l'esecuzione puo' rimane definita l'area di memoria condivisa. se quando avete "ucciso" il processo lock valeva 1, il programma non funzionera' piu'. In GNU-Linux le aree di memoria condivisa sono visibili nel file system nella directory "/dev/shm", quindi occorre cancellare lo pseudo-file "/dev/shm/test".