互斥量、应用函数、加锁解锁、死锁、读写锁

条件变量、信号量、互斥量实现进程间同步、

互斥量mutex

  1. linux 中提高一把互斥锁mutex(称之为互斥量)
  2. 每个线程在对资源操作前尝试先加锁,成功加锁才能,操作结束解锁。

资源还是共享,线程间也还是竞争,但通过“锁”就将资源的访问变成互斥操作,而后与时间有关的错误也不会再产生。

注意:同一时刻,只能有一个线程持有该锁。

A线程对某个全局变量加锁访问B在访问前尝试加锁,拿不到锁,B阻塞C线程不去加锁,而直接访问该全局变量,但出现数据混乱。所以互斥锁实质上是操作系统提高一把“建议锁”(协同锁),建议程序中有多线程访问共享资源的时候使用该机制。但并没有强制限定

主要应用函数

pthread_mutex_init();
pthread_mutex_destroy();
pthread_mutex_lock();
pthread_mutex_trylock();//轮询方式加锁
pthread_mutex_unlock();
//函数返回值,成功:0,失败返回错误号

pthread_mutex_t 类型,其本质是一个结构体。可简化理解,应用时忽略其实现细节,简单当成整数看待。

pthread_mutex_t mutex;//变量mutex只有两种值1,0。初始化成功为1

pthread_mutex_init函数

初始化一个互斥锁(互斥量),初值可看作1

int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
//参1:传出参数,调用时应传&mutex
//参2:互斥量属性,一个传入参数,通常传NULL,选用默认属性(线程间共享)

restrict关键字:只限用于限制指针,告诉编译器,所有修改该指针指向内存中内容的操作,只能通过本指针完成不能通过除本指针以外的其他变量或者指针修改。

静态初始化:如果互斥锁mutex是静态分配的(定义在全局,或加了static关键字修饰),可直接使用宏进行初始化。

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

动态初始化:局部变量应采用动态初始化

pthread_mutex_init(&mutex,NULL);

pthread_mutex_destory函数

销毁一个互斥锁

int pthread_mutex_destory(pthread_mutex_t *mutex);

pthread_mutex_lock函数

加锁,可理解为将mutex--(或-1)

int pthread_mutex_lock(pthread_mutex_t *mutex);

pthread_mutex_unlock函数

解锁,可理解为将mutex++(或+1)

int pthread_mutex_unlock(pthread_mutex_t *mutex);

pthread_mutex_trylock函数

尝试加锁,(解锁时不会阻塞)

int pthread_mutex_trylock(pthread_mutex_t *mutex);

加锁和解锁

lock尝试加锁,如果加锁不成功,线程阻塞,阻塞到持有该互斥量的其他线程解锁为止

unlock主动解锁函数,同时将阻塞在该锁上的所有线程全部唤醒,至于哪个线程先被唤醒,取决于优先级、调度。默认:先阻塞、先唤醒

如:T1 T2 T3 T4 使用一把mutex锁。T1加锁成功,其他线程均阻塞,直到T1解锁。T1解锁后,T2,T3,T4均被唤醒,并自动再次尝试加锁。可假想mutex锁init成功初值1,lock功能是将mutex--,unlock将mutex++

lock加锁失败会阻塞,等待锁释放;

trylock加锁失败直接返回错误号(如:EBUSY),不阻塞。

加锁测试 【mutex.c】

#include<stdio.h>
#include <pthread.h>
#include <unistd.h>

pthread_mutex_t mutex;

void *tfn(void *arg){
        srand(time(NULL));
        while(1){
                pthread_mutex_lock(&mutex);
                printf("hello ");
                sleep(rand()%3);//模拟长时间操作共享资源,导致cpu易主,产生与时间有关的错误
                printf("world\n");
                sleep(rand()%3);
        }
        return NULL;
}       
                
int main(){
        int flg = 5;
        pthread_t tid;
                
        pthread_mutex_init(&mutex,NULL);//初始化一个互斥锁
        pthread_create(&tid,NULL,tfn,NULL);//创建一个线程
        while(flg--){
                pthread_mutex_lock(&mutex);//给互斥锁加锁
        
                printf("HELLO");
                sleep(rand()%3);
                printf("WORLD\n");
                pthread_mutex_unlock(&mutex);//给互斥锁解锁
                sleep(rand()%3);
        }
        pthread_cancel(tid);//杀死线程
        pthread_join(tid,NULL);//阻塞等待线程退出,获取线程退出状态
        pthread_mutex_destory(&mutex);//销毁互斥锁

        return 0;
}

该程序由于没有共享和竞争而没有加任何同步机制,导致产生时间有关的错误,造成数据混乱。

【练习】修改上述程序,使用mutex互斥锁进行同步。

  1. 定义全局互斥量,初始化init(&m,NULL)互斥量,添加对应的destory
  2. 两个线程while中,两次printf前后,分别加lock和unlock
  3. 将unlock挪至第二个sleep后,发现交替现象很难出现。线程在操作完共享资源后本应该立即解锁,但修改后,线程抱着锁睡眠,睡醒解锁后有立即加锁,这两个库函数本身不会阻塞。所以在两行代码之间失去CPU的概率很小,因此,另一个线性很难得到加锁机会。
  4. main中加flag=5将flg在while中--,这时,主线程输出5次后试图销毁锁,但子线程未将锁释放,无法完成。
  5. main中加pthread_cancel()将子线程取消。

【pthrd_mutex.c】

结论:在访问共享资源前加锁,访问结束后立即加锁。锁的“粒度”应越小越好。

死锁

  1. 线程试图对同一个互斥量A加锁两次
  2. 线程1拥有A锁,请求获得B锁;线程2拥有B锁,请求获得A锁。

【作业】编写程序,实现上述两种死锁现象

 

读写锁

与互斥量类似,但读写锁允许更高的并行性,其特性为:写读占,读共享

读写锁状态

一把读写锁具备三种状态(读写是一把锁,两种状态)

  1. 读模式下加锁状态(读锁)
  2. 写模式下加锁状态(写锁)
  3. 不加锁状态

读写锁特性

  1. 读写锁是“写模式式加锁”时,解锁前,所有对该锁加锁的线程都会被阻塞
  2. 读写锁是“读模式加锁”时,如果线程以读模式对其加锁会成功;如果线程以写模式加锁会阻塞。
  3. 读写锁是“读模式加锁”时,既有试图以写模式加锁的线程,也有试图以读模式加锁的线程,那么读写锁会阻塞随后的读模式锁请求,优先满足写模式锁。读锁、写锁并行阻塞,写锁优先级高

读写锁叫共享-独占锁。当读写锁以读模式锁住时,它是以共享模式锁住;当它以写模式锁住时,它是以独占模式锁住的,写独占,读共享

读写锁非常合适与对数据结构读的次数远大于写的情况。

主要应用函数

pthread_rwlock_init函数

用于初始化一把读写锁

int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restrict attr);
//参数2:attr表速写锁属性,通常使用默认属性,传NULL即可 

pthread_rwlock_destory函数

销毁一把读写锁

int pthread_rwlock_destory(pthread_rwlock_t *rwlock);

pthread_rwlock_rdlock函数

用于以读写式请求读写锁(又称:请求读锁)

int pthread_rwlock_rdlock(pthread_rwkock_t *rwlock);

pthread_rwlock_wdlock函数

以写方式请求读写锁(请求写锁)

int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

pthread_rwlock_unlock函数

解锁

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

pthread_rwlock_tryrdlock函数

非阻塞以读方式请求读写锁(非阻塞请求读锁

int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

pthread_rwlock_trywrlock函数

非阻塞以写方式请求读写锁(非阻塞请求写锁

读写锁例子:同时有多个线程对同一个全局数据读、写操作、【rwlock.c】

 

条件变量

条件变量本身不是锁,但它可以造成线程阻塞,通常与互斥锁配合使用。给多线程提供一个会合的场所。

主要应用函数

pthread_cond_init 函数

初始化一个条件变量

int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);		
//参数2:attr表条件变量属性,通常默认值,传NULL即可
//用静态初始化的方法,初始化条件变量:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

pthread_cond_destroy函数

销毁一个条件变量

int pthread_cond_destroy(pthread_cond_t *cond);

pthread_cond_wait函数

阻塞等待一个条件变量

 int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

作用:

  1. 阻塞等待条件变量cond(参1)满足
  2. 释放已掌握的互斥锁(解锁互斥量)相当于pthread_mutex_unlock(&mutex);
  3. 当被唤醒,pthread_cond_wait函数返回时,解除阻塞并重新申请获取互斥锁 pthread_mutex(&mutex)

pthread_cond_timedwait函数

限时等待一个条件变量

int pthread_cond_timewait(pthread_cond_t *restrict,pthread_mutex_t *restrict mutex,const struct timespec *restrict abstime);
//参3:为一个结构体 sturct timespec{time_t tv_sec; long tv_nsec;}

形参abstime:绝对时间

如:time(NULL)返回为绝对时间,而alarm(1)是相对时间,相对当前时间定时1秒钟。

time_t cur = time(NULL);//获取当前时间
struct timespec t;//定义timespec结构体变量t
t.tv_sec = cur+1;//定时1秒
pthread_cond_timewait(&cond,&mutex,&t);//传参

 

 

 

用餐模型分析

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>

struct msg{
        int num;
        struct msg *next;
};

struct msg *head = NULL;
struct msg *mp = NULL;

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;

void *producter(void *arg){
        while(1){
                mp = malloc(sizeof(struct msg));
                mp->num = rand()%400+1;
                printf("---producted -- %d\n",mp->num);

                pthread_mutex_lock(&mutex);
                mp->next = head;//此时mp变为头结点(在head之前插入)
                head = mp;
                pthread_mutex_unlock(&mutex);

                pthread_cond_signal(&has_product);
                sleep(rand()%3);
        }
        return NULL;
}

void *consumer(void *arg){
        while(1){
                pthread_mutex_lock(&mutex);
                while(head == NULL){
                        pthread_cond_wait(&has_product,&mutex);
                }
                mp = head;
                head = mp->next;
                pthread_mutex_unlock(&mutex);

                printf("--consumer --%d\n",mp->num);
                free(mp);
                mp=NULL;
                sleep(rand()%3);
        }
        return NULL;
}
int main(void){
        pthread_t ptid,ctid;

        pthread_create(&ptid,NULL,producter,NULL);//线程号,线程属性(NULL为默认),回调函数,传入参数
        pthread_create(&ctid,NULL,consumer,NULL);

        pthread_join(ptid,NULL);
        pthread_join(ctid,NULL);

        return 0;
}

asd

asdf

哲学家用餐模型

#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#include<stdlib.h>
#include<semaphore.h>
#include<sys/mman.h>
#include<sys/wait.h>


int main(){
        //创建5个进程
        //创建5只筷子作为锁
        //实现强筷子吃面
        int i;
        pid_t pid;
        sem_t *s;
        s = mmap(NULL,sizeof(sem_t)*5,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANON,-1,0);//映射区?
        if(s == MAP_FAILED){
                perror("fail to mmap");
                exit(1);
        }

        for(i=0;i<5;i++){
                sem_init(&s[i],0,1);//信号量 ,信号在各进程间是否共享,信号量初值1,(信号量变为互斥锁)
        }
        for(i=0;i<5;i++)
                if((pid = fork())==0) //循环创建进程
                        break;

        if(i<5){
                int l,r;
                srand(time(NULL));
                if(i == 4)
                        l=0,r=4;//左右解锁编号
                else
                        l=i,r=i+1;
                while(1){
                        sem_wait(&s[l]);//给第l信号量加锁
                        if(sem_trywait(&s[r]) == 0){//尝试对第r信号加锁
                                printf("%c is eating \n",'A'+i);
                                sem_post(&s[r]);//对第r信号解锁
                        }
                        sem_post(&s[l]);//对第l信号解锁
                        sleep(rand()%5);
                }
                exit(0);
        }

        //回收子进程
        for(i=0;i<5;i++)
                wait(NULL);
        //销毁信号量
        for(i=0;i<5;i++)
                sem_destroy(&s[i]);

        //
        munmap(s,sizeof(sem_t)*5);

        return 0;
}

 

Logo

更多推荐