8. Säikeet
Prosessi on suoritusympäristö + useita säikeitä. Säien on käyttöjärjestelmän abstraktio aktiviteetista eli suorituspolusta. Suoritusympäristö on resurssinhallintayksikkö. Säikeellä omaa:
- Suorituksen "lanka" eli kutsupino
- Kutsujen parametrit
- Kutsujen paikalliset muuttujat
- errno
- signaalipeite
Säikeille yhteistä mm.:
- ohjelman argc, argv ympäristö
- globaalit muuttujat
- tiedostokuvaajat
- fcntl-lukot
- resurssirajat
- pääsyoikeudet
POSIX vaatii käyttäjän määrittelevän muuttujan tyyppiä pthread_t, jonka avulla säie identifioidaan. Säie luodaan kutsulla:
int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
Kutsun onnistuessa uuden säikeen tunnus tallenetaan parametriin thread ja funktio palauttaa arvon 0. Virhetilanteessa nollasta poikkeava arvo palautuu. Esimerkiksi jos luodaan säie suorittamaan funktiota f():
#include < pthread.h > ... pthread_t thread; pthread_create(&thread, NULL, f, &arg). ... The routine f() must have the prototype: void *f(void *arg);
Ohjelman viimeiseksi toimeksi täytyy odottaa kaikkien säikeiden päättymistä ennenkuin voi käsitellä funktion f() paluuarvoa. Odottaminen tapahtuu funktiolla:
int pthread_join(pthread_t th, void **thread_return);Funktion kutsuja jää odottamaan kunnes säie, jonka identifikaation on th päättyy.
Nopein tapa välittää informaatiota säikeiden välillä on yhteinen muisti. Tämä kuitenkin vaatii synkronointia säikeiden välillä. Synkronointia varten tarvitaan erityinen synkronointimuuttuja, joka luodaan funktiolla:
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);
Esimerkiksi:
#include < pthread.h > ... pthread_mutex_t lock; pthread_mutex_init(&lock,NULL); ...Esimerkki:
/* use gcc -lpthread to compile */ #include < stdio.h > #include < pthread.h > /* definition of a suitable structure */ typedef struct { double volatile *p_s; /* the shared value of scalar product */ pthread_mutex_t *p_s_lock; /* the lock for variable s */ int n; /* the number of the thread */ int nproc; /* the number of processors to exploit */ double *x; /* data for first vector */ double *y; /* data for second vector */ int l; /* length of vectors */ } DATA; void *SMP_scalprod(void *arg) { register double localsum; long i; DATA D = *(DATA *)arg; localsum = 0.0; /* Each thread start calculating the scalar product from i = D.n with D.n = 1, 2, ... , D.nproc. Since there are exactly D.nproc threads the increment on i is just D.nproc */ for(i=D.n;i < D.l;i+=D.nproc) localsum += D.x[i]*D.y[i]; /* the thread assert the lock on s ... */ pthread_mutex_lock(D.p_s_lock); /* ... change the value of s ... */ *(D.p_s) += localsum; /* ... and remove the lock */ pthread_mutex_unlock(D.p_s_lock); return NULL; } #define L 9 /* dimension of vectors */ int main(int argc, char **argv) { pthread_t *thread; void *retval; int cpu, i; DATA *A; volatile double s=0; /* the shared variable */ pthread_mutex_t s_lock; double x[L], y[L]; if(argc != 2) { printf("usage: %s\n", argv[0]); exit(1); } cpu = atoi(argv[1]); thread = (pthread_t *) calloc(cpu, sizeof(pthread_t)); A = (DATA *)calloc(cpu, sizeof(DATA)); for(i=0;i < L;i++) x[i] = y[i] = i; /* initialize the lock variable */ pthread_mutex_init(&s_lock, NULL); for(i=0;i < cpu;i++) { /* initialize the structure */ A[i].n = i; /* the number of the thread */ A[i].x = x; A[i].y = y; A[i].l = L; A[i].nproc = cpu; /* the number of CPU */ A[i].p_s = &s; A[i].p_s_lock = &s_lock; if(pthread_create(&thread[i], NULL, SMP_scalprod, &A[i] )) { fprintf(stderr, "%s: cannot make thread\n", argv[0]); exit(1); } } for(i=0;i < cpu;i++) { if(pthread_join(thread[i], &retval)) { fprintf(stderr, "%s: cannot join thread\n", argv[0]); exit(1); } } printf("s = %f\n", s); exit(0); }
Jan Lindström (Jan.Lindstrom@cs.Helsinki.FI)