线程的同步互斥
[toc]
《操作系统》同步互斥
引起同步互斥问题的原因
当下人们需要让进程在短时间内同时完成不止一件事情,每个线程处理各自独立的任务。线程是进程的更小分支,每一线程完成进程的一部分任务,但系统并不给线程分配任何资源,它共享创建它的进程所拥有的资源。但是当一个线程修改变量时,其它线程在读取这个变量时可能读取到不一致的值,无法区分到底是读取了修改前的值,还是修改后的值,导致了程序执行结果无法复现,所以就引入了同步互斥,来解决进程内的资源分配问题。
同步互斥方法说明
互斥锁 同步互斥方法说明
互斥锁,一个线程在进入临界区时应得到锁,在它退出时释放锁,以让其它需要的线程访问这个临界区。对于获得锁的进程,它会执行临界区的代码,同时其它未获得锁的线程会被阻塞,直到得到锁才会进入临界区。
自旋锁 同步互斥方法说明
自旋锁与互斥锁原理基本相同,不同之处在于未获得锁时被阻塞的方法实现不同,互斥锁通过硬件方法阻塞,而自旋锁通过软件方法,即让线程空循环来等待。
信号量 同步互斥方法说明
同步,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动作。而且信号量有一个更加强大的功能,信号量可以用作为资源计数器,把信号量的值初始化为某个资源当前可用的数量,使用一个之后递减,归还一个之后递增。
条件变量 同步互斥方法说明
条件变量,用while循环作判断条件,循环条件满足线程进入工作队列等待,直到其它线程的执行使得条件满足后,该线程才会跳出循环,继续执行剩余代码。
屏障 同步互斥方法说明
屏障允许等待任意数目的线程都到达某一点,直到到达该点的线程达到规定数目,然后从该点继续执行,而不用线程退出。
读写锁 同步互斥方法说明
读写与互斥量类似,读写锁有3种状态,读模式加锁,写模式加锁,不加锁。一次只能有一个线程占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。
同步互斥方法实现
互斥锁 同步互斥方法实现
互斥锁 同步互斥示例代码
#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]; //创建线程pid
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 自旋锁 同步互斥示例代码
#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]; //创建线程pid
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); // 释放锁
号量 同步互斥方法实现
信号量 同步互斥示例代码
#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); //empty = 0 则阻塞,empty > 0则获取一个信号量,向下执行
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);
销毁信号量。
条件变量 同步互斥方法实现
条件变量 同步互斥示例代码
#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);
该函数通知线程条件已满足,至少能够唤醒一个等待该条件的线程
屏障 同步互斥方法实现
屏障 同步互斥示例代码
#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);
当代其它线程到达屏障,当线程数量不满足时,已到达的线程会休眠, 直到最后一个线程到达屏障,满足屏障计数,所有线程都被唤醒。
读写锁 同步互斥方法实现
读写锁 同步互斥示例代码
#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。