[toc]
《操作系统》同步互斥 引起同步互斥问题的原因 当下人们需要让进程在短时间内同时完成不止一件事情,每个线程处理各自独立的任务。线程是进程的更小分支,每一线程完成进程的一部分任务,但系统并不给线程分配任何资源,它共享创建它的进程所拥有的资源。但是当一个线程修改变量时,其它线程在读取这个变量时可能读取到不一致的值,无法区分到底是读取了修改前的值,还是修改后的值,导致了程序执行结果无法复现,所以就引入了同步互斥,来解决进程内的资源分配问题。
同步互斥方法说明 互斥锁 同步互斥方法说明 互斥锁,一个线程在进入临界区时应得到锁,在它退出时释放锁,以让其它需要的线程访问这个临界区。对于获得锁的进程,它会执行临界区的代码,同时其它未获得锁的线程会被阻塞,直到得到锁才会进入临界区。
自旋锁 同步互斥方法说明 自旋锁与互斥锁原理基本相同,不同之处在于未获得锁时被阻塞的方法实现不同,互斥锁通过硬件方法阻塞,而自旋锁通过软件方法,即让线程空循环来等待。
信号量 同步互斥方法说明 同步,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动作。而且信号量有一个更加强大的功能,信号量可以用作为资源计数器,把信号量的值初始化为某个资源当前可用的数量,使用一个之后递减,归还一个之后递增。
条件变量 同步互斥方法说明 条件变量,用while循环作判断条件,循环条件满足线程进入工作队列等待,直到其它线程的执行使得条件满足后,该线程才会跳出循环,继续执行剩余代码。
屏障 同步互斥方法说明 屏障允许等待任意数目的线程都到达某一点,直到到达该点的线程达到规定数目,然后从该点继续执行,而不用线程退出。
读写锁 同步互斥方法说明 读写与互斥量类似,读写锁有3种状态,读模式加锁,写模式加锁,不加锁。一次只能有一个线程占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。
同步互斥方法实现 互斥锁 同步互斥方法实现 互斥锁 同步互斥示例代码1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #include <stdio.h> #include <pthread.h> #include <unistd.h> int ticketAmount = 2 ; pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;void * ticketAgent (void * arg) { pthread_mutex_lock(&lock); int t = ticketAmount; if (t > 0 ) { printf ("售出一张票!\n" ); t--; }else { printf ("票已经卖完了!!\n" ); } ticketAmount = t; pthread_mutex_unlock(&lock); pthread_exit(0 ); } int main (int argc, char const *argv[]) { pthread_t ticketAgent_tid[2 ]; for (int i = 0 ; i < 2 ; ++i) { pthread_create(ticketAgent_tid+i, NULL , ticketAgent,NULL ); } for (int i = 0 ; i < 2 ; ++i) { pthread_join(ticketAgent_tid[i],NULL ); } printf ("还剩下 %d张票\n" , ticketAmount); return 0 ; }
未加锁:
加锁:
3.1.2 互斥锁 同步互斥关键代码说明pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; //创建全局锁并初始化
pthread_mutex_lock(&lock); //上锁
pthread_mutex_unlock(&lock); //开锁
3.2 自旋锁 同步互斥方法实现 3.2.1 自旋锁 同步互斥示例代码1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include <stdio.h> #include <pthread.h> #include <unistd.h> int ticketAmount = 2 ; pthread_spinlock_t lock;void * ticketAgent (void * arg) { pthread_spin_lock(&lock); int t = ticketAmount; if (t > 0 ) { printf ("售出一张票!\n" ); t--; }else { printf ("票已经卖完了!!\n" ); } ticketAmount = t; pthread_spin_unlock(&lock); pthread_exit(0 ); } int main (int argc, char const *argv[]) { pthread_spin_init(&lock,PTHREAD_PROCESS_PRIVATE); pthread_t ticketAgent_tid[2 ]; for (int i = 0 ; i < 2 ; ++i) { pthread_create(ticketAgent_tid+i, NULL , ticketAgent,NULL ); } for (int i = 0 ; i < 2 ; ++i) { pthread_join(ticketAgent_tid[i],NULL ); } printf ("还剩下 %d张票\n" , ticketAmount); pthread_spin_destroy(&lock); return 0 ; }
未加锁:
加锁:
自旋锁 同步互斥关键代码说明int pthread_spin_init(pthread_spinlock_t *lock, int pshared); //初始化自旋锁,pshared 参数表示自旋锁是否能被其它进程共享。
int pthread_spin_destroy(pthread_spinlock_t *lock); //销毁自旋锁,释放其资源
Int pthread_spin_lock(pthread_spinlock_t *lock); // 获得锁,如果锁未被占用,则将锁锁上,防止其它进程获得锁,如果锁被占中,则线程将一直循环等待,直到锁被释放获得锁
Int pthread_spin_unlock(pthrad_spinlock_t *lock); // 释放锁
号量 同步互斥方法实现 信号量 同步互斥示例代码1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 #include <stdio.h> #include <sys/types.h> #include <unistd.h> #include <pthread.h> #include <stdlib.h> #include <time.h> #include <semaphore.h> #define NEXTP 1 #define NEXTC 0 int full_v = 0 ; int empty_v = 5 ; int buff[5 ]={0 }; int in = 0 ; int out = 0 ; sem_t empty;sem_t full;sem_t mutex;void print (int *buff,int k) { printf ("{" ); for (int i = 0 ; i < k; i++){ printf ("%d" ,*(buff+i)); if (i == k - 1 ) continue ; printf ("," ); } printf ("}\n" ); } void * producer (void * arg) { while (1 ){ sem_wait(&empty); if (empty_v != 0 ){ empty_v--; sem_wait(&mutex); buff[in] = NEXTP; in = (in + 1 )% 5 ; print(buff,5 ); sem_post(&mutex); full_v++; sem_post(&full); } sleep(0.5 ); } pthread_exit(0 ); } void * consumer (void * arg) { while (1 ){ sem_wait(&full); if (full_v != 0 ){ full_v--; sem_wait(&mutex); buff[out] = NEXTC; out = (out + 1 )% 5 ; print(buff,5 ); sem_post(&mutex); empty_v++; sem_post(&empty); } sleep(1 ); } pthread_exit(0 ); } int main () { sem_init(&empty,0 ,5 ); sem_init(&full,0 ,0 ); sem_init(&mutex,0 ,1 ); pthread_t tid1,tid2; pthread_create(&tid1,NULL ,producer,NULL ); pthread_create(&tid2,NULL ,consumer,NULL ); pthread_join(tid1,NULL ); pthread_join(tid2,NULL ); sem_destroy(&empty); sem_destroy(&full); sem_destroy(&mutex); return 0 ; }
未加锁:
加锁:
信号量 同步互斥关键代码说明要使用信号量,先包含头文件<semaphore.h>
sem_t:信号量的数据类型,实际上是个长整型,但除P,V操作外不能对它执行加减操作
int sem_init(sem_t *sem, int pshared, unsigned int val);
第一个参数为信号量指针,第二个参数为信号量类型(一般设置为0),第三个为信号量初始值。 第二个参数pshared为0时,该进程内所有线程可用,不为0时不同进程间可用。
int sem_wait(sem_t *sem);
申请一个信号量,当前无可用信号量则等待,有可用信号量时占用一个信号量,对信号量的值减1。
int sem_post(sem_t *sem);
释放一个信号量,信号量的值加1。
int sem_destory(sem_t *sem);
销毁信号量。
条件变量 同步互斥方法实现 条件变量 同步互斥示例代码1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 #include <stdio.h> #include <pthread.h> #include <unistd.h> int dining_sum = 0 ; pthread_mutex_t mutex[5 ] = { PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER, PTHREAD_MUTEX_INITIALIZER }; pthread_cond_t conds[5 ] = { PTHREAD_COND_INITIALIZER, PTHREAD_COND_INITIALIZER, PTHREAD_COND_INITIALIZER, PTHREAD_COND_INITIALIZER, PTHREAD_COND_INITIALIZER }; int n = 0 ; void pickup_forks (int philosopher_number) { pthread_mutex_lock(&mutex[philosopher_number]); while (dining_sum >= 5 ) { printf ("正在挨饿。。。 {%u}\n" ,pthread_self()); pthread_cond_wait(&conds[philosopher_number],&mutex[philosopher_number]); } dining_sum++; printf ("正在吃饭。。。 {%u}\n" ,pthread_self()); sleep(0.5 ); pthread_mutex_unlock(&mutex[philosopher_number]); } void return_forks (int philosopher_number) { pthread_mutex_lock(&mutex[philosopher_number]); printf ("吃饱了,开始思考问题! {%u}\n" ,pthread_self()); dining_sum--; printf ("思考完了!!{%u}\n" ,pthread_self()); pthread_cond_signal (&conds[philosopher_number]); pthread_mutex_unlock(&mutex[philosopher_number]); } void * philosophers (void * arg) { while (1 ){ pickup_forks(*(int *)arg); return_forks(*(int *)arg); } } int main () { pthread_t pid[5 ]; for (int k = 0 ; k < 5 ; k++) pthread_create(&pid[k], NULL , philosophers,(void *)&k); for (int k = 0 ; k < 5 ; k++) pthread_join(pid[k],NULL ); return 0 ; }
加锁:
条件变量 同步互斥关键代码说明int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
初始化条件变量,cond 指明其id,attr 指明其属性
int pthread_cond_destroy(pthread_cond_t *cond);
销毁条件变量
int pthread_cond_wait(pthread_cond_t *restict cond ,pthread_mutex_t *restrict mutex);
互斥量对此函数进行保护。调用者把锁住的互斥量传给函数,函数然后自动把调用线程放到等待条件的线程列表上,对互斥量解锁,函数返回时,互斥量再次被锁住。
int pthread_cond_signal(pthread_cond_t *cond);
该函数通知线程条件已满足,至少能够唤醒一个等待该条件的线程
屏障 同步互斥方法实现 屏障 同步互斥示例代码1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include <stdio.h> #include <pthread.h> #include <unistd.h> #define DINNERS 3 pthread_barrier_t barrier;char *dinners[DINNERS] = {"爸爸" ,"妈妈" ,"我" };void * person (void * arg) { int i = (int )arg; printf ("%s入席\n" ,dinners[i]); pthread_barrier_wait(&barrier); pthread_exit(0 ); } int main () { pthread_barrier_init(&barrier,NULL ,DINNERS); pthread_t pid[DINNERS]; int k; for (k = 0 ; k < DINNERS; k++){ int err = pthread_create(&pid[k], NULL , person,(void *)k); if (err != 0 ) { printf ("线程创建失败!" ); return -1 ; } } for (k = 0 ; k < DINNERS; k++) pthread_join(pid[k],NULL ); printf ("大家开始吃饭!\n" ); pthread_barrier_destroy(&barrier); return 0 ; }
未加锁:警告不用管
加锁:警告不用管
屏障 同步互斥关键代码说明int pthread_barrier_init(pthread_barrier_t *restrict barrier, const pthread_barrierattr_t *restrict attr, unsigned int count);
对屏障初始化,count规定到达屏障线程的数目。
int pthread_barrier_destroy(pthread_barrier_t *barrier);
销毁屏障,释放其资源
int pthread_barrier_wait(pthread_barrier_t *barrier);
当代其它线程到达屏障,当线程数量不满足时,已到达的线程会休眠, 直到最后一个线程到达屏障,满足屏障计数,所有线程都被唤醒。
读写锁 同步互斥方法实现 读写锁 同步互斥示例代码1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 #include <stdio.h> #include <pthread.h> #include <unistd.h> #include <semaphore.h> #include <time.h> pthread_rwlock_t rwlock;int num = -1 ; void * writer (void * arg) { pthread_rwlock_wrlock(&rwlock); printf ("Writing [%d]" ,(int *)arg); num = (int *)arg; printf ("----:%d\n" ,num); pthread_rwlock_unlock(&rwlock); pthread_exit(0 ); } void * reader (void * arg) { pthread_rwlock_rdlock(&rwlock); printf ("Reading [%d]----:%d\n" ,(int *)arg,num); pthread_rwlock_unlock(&rwlock); pthread_exit(0 ); } int main () { pthread_rwlock_init(&rwlock,0 ); pthread_t pid1[5 ],pid2[2 ]; for (int i=0 ; i < 5 ; i++) pthread_create(&pid1[i],NULL ,reader,(void *)i); for (int i=0 ; i < 2 ; i++) pthread_create(&pid2[i],NULL ,writer,(void *)i); for (int i=0 ; i < 5 ; i++) pthread_join(pid1[i],NULL ); for (int i=0 ; i < 2 ; i++) pthread_join(pid2[i],NULL ); pthread_rwlock_destroy(&rwlock); return 0 ; }
未加锁:
加锁:
3.6.2 读写锁 同步互斥关键代码说明int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
初始化读写锁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
销毁读写锁,释放其资源
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
获取读状态的读写锁,允许多个线程进行读访问
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
获取写状态的读写锁,只允许一个线程进行写访问
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
解锁,不管是读状态还是写状态,都能用此函数进行解锁
实验总结光看书不动手是学不到东西的,学中做,做中学。
参考书籍亚伯拉罕·西尔伯沙茨 等 著,郑扣根 译。操作系统概念(原书第9版)。 机械工业出版社,ISBN:9787111604365,2018。
[美] W.,理查德·史蒂文斯(W.,Richard,Stevens)史蒂芬·A.,拉戈 著, 戚正伟,张亚英,尤晋元 译。Unix环境高级编程。 人民邮电出版社,ISBN:9787115516756,2019。