26. linux系统基础09-线程同步1 互锁1.1 互斥锁的使用步1.2 练习1.3 死销2 读写锁3条件变量4信号量

守护进程的基本概念,守护进程的特点,这个要求你理解,起码我给你说个东西你得知道守护进程到底是怎么回事

 

 

这个模型咱们就直接看咱们的例子, 优化的作业,原有的基础上进行优化,那么优化的方向 我已经给你说好了,第一个优化 不再频繁打开与关闭,第二个优化 这个文件不要太大了,到一定的程度 记得改名,保存一下,顺着这两条线给大家说一下,代码我直接打开了,

 

 

这个代码还是在原有的基础上进行修改,

大体再说一下,第一步 创建守护进程,

 父进程fork子进程,然后父进程退出,这两步是不是异常和父进程成功创建的过程,我是不是都给你合成一步了?44-48

第二步调用setsid 那么这个函数的意思是什么呀?调用这个函数的目的是不是创建会话呀?创建一个会话,那么创建会话成功以后,那么这个进程是不是就成了一个会长啊?是不是也是组长啊?是不是也摆脱了控制终端对他的影响啊? 三个效果

改变当前的工作目录54 这个是不是必须的?不是必须的,你可以改的,你也可以有他 也可以没有他,有没有看具体需求吧?

//重设文件掩码umask(0000);这个是不是必须的? 不是吧?为什么这一步有的时候我们需要加上?为什么呀?如果你没有这个话呢,你父进程对文件操作的权限如果说比较严格的话,比如说他就没有读写权限,这样的话你新建的这个文件是不是就没有读写权限啊?因为子进程的umask的值 是不是从父进程继承过来的啊?或者叫复制过来的呀?他的掩码和父进程一模一样,在这一步 我们之所以这样做的目的就是 让我们这个子进程 操作文件的时候 灵活一些,这一步是不是也可以省略,如果说你的父进程就是这一个,或者是0002是不是也没事啊?通常umask掩码默认有两个,一个是0002 一个是00022 两个,这两个对用户的权限没有影响吧?

 60-62 这一步是关闭 这三个文件描述符,关闭这三个文件描述符的作用,是什么呢?本来是节省资源吧?文件描述符是不是也是一个资源啊?在我们这个文件描述表当中是不是一共可以打开1024个文件吧?这一步是不是也可以省略, 当然正常情况下,我们这个进程的话能打开这么多文件吗?除非你循环,打开不关闭,才有可能,否则的话,一般情况下,我打开文件用完之后我就关闭了,所以说这一个即使你不关闭,影响也不大,

后面是核心工作,我们的核心工作是不是每隔两秒钟 是不是将系统时间写入文件啊?65-69 那个过程 我给大家分析了,用到的知识点 也都给你说过了,在这我们是不是首先 注册一个信号处理函数啊? 调用的是哪个函数呀?sigaction( 这个函数是不是也可以用signo代替呀? 当然signo在不同的 平台上他的行为不一样,所以大家在用的时候 建议用sigaction

 72-77这一步是不是设立时钟啊?这个时钟是周期性触发的一个时钟,比alarm是不是要好一些啊?他可以重复 周期性的触发,

接下来 咱们进入一个while 循环, 进入while循环的目的是不是让我们的父进程不退出啊? 让这个进程不退出,也就是说我们这个守护进程 应该是一直运行的周期性运行的后台进程啊?你肯定你一般这个守护进程都是写在这个while语句循环里面,接下来咱们看一下 信号处理函数,

这个信号处理函数 就是什么呀?获取系统时间吧? 并把这个时间是不是写入文件啊?

 整体上 这个流程已经是和咱们前天讲过的是不是一样了?

 接下来咱们优化,看看是怎么优化的, 

这个优化是不要重复的打开 和关闭文件,

 首先,我在这里定义一个flag  第一次的时候,这个flag是不是0 啊?也就是说如果flag=0,是不是我们需要新建一个文件啊?在这是不是新建文件啊?新建文件之后呢,我们又把这个flag=1了,

 置为1之后,下次再调用这个函数的话,那么这个是不是下次就不再执行了?是不是相当于我加了一个开关呀?就这个开关,在咱们讲信号的时候呢,那个数数是不是类似呀?这个开关 一开始打开, 打开之后呢 就打开一次吧?接下来是不是就关闭了?这个开关你关上了,也就是说flag =1 这个条件不再满足,所以呢,这个里面的这句话不再执行,

接着看, 然后写文件,

 那么如果说这个文件写到一定的大小,那么这个文件是不是我们要求的让他重新复制出一份来呀?然后新建一个名字啊?

怎么做的呢?我们是不是要获取一个文件大小啊?用lseek函数获取文件大小,当然这个fd是不是定义的全局变量啊? 这个flag 是不是也是全局变量啊? 这个是不是我们可以定义为static变量啊?如果你定义static 变量 你这个 flag 是不是就可以写在里面了?

当然你这样写的话,你这样87 就不能这个flag 了吧?最好还是定义为全局变量,你可以定义成全局静态变量也可以,总之你要让 这个函数是不是能看到他呀? 那么接着看 在while语句循环里面呢,我们先获取文件大小,用的是lseek吧?获取文件大小之后呢,我们是不是把这个 比如说这个比如说这个字节83 数超过了100了 是不是要吧这个文件关闭,关闭之后我做了一个这样的操作,rename 是不是相当于move呀?move是不是有重命名的意思啊?这个就类似于那个 和那个一样,rename  把这个./mydaemon.log 给他复制一个新的副本出来吧?也就是相当于做了一个保存吧?这样做的目的是不是为了使我们的文件不过于太大呀?

接下来flag =0 是不是这个文件,打开这个文件的开关就开了,如果说信号再产生的话,这儿的话这个if(flag==0) 24  这个时候 是不是还可以再新建一个文件啊? 文件名是不是还是这个?mydaemon .log 27 

这儿写个for循环 定义一个i变量,这儿是不是.1 .2也可以呀?你这个会了 那个会吗?这个你只要把这个看懂了,是不是稍加改造就可以改成/mydaemon.log.1 .log.2  .log.3是不是这样的?第二次的话肯定被覆盖掉了吧?当然你知道怎么回事就可以了86,

那么也就是说你这个文件大小超过100以后呢,那么我们关闭文件,然后把这个文件保存出去一份,然后flag =0 打开开关,打开开关以后,然后再进入到信号处理函数里面去,24-34以后,那么我这个开关是不是已经开了?开关开了以后那么是不是我就可以新建文件了,新建文件之后是不是又关了flag =1,当然实际上 在生产环境当中 你这个日志文件不可能是100 是不是有可能是比如说10M 5M都行,但 总之100太少了,如果100的话 是不是他这一天会建无数个这样的文件啊?肯定不能这样,一天肯定有个几个,这是正常的,而且正常情况下,我们日志文件名命名的时候是这样的,是这样的,/mydaemon .log  或者/mydaemon_20180827 .1.log  /mydaemon_20180827 .2.log  /mydaemon_20180827 .3.log 肯定不能是 ,因为有可能就这一天的话交易量比较大,你比如说像淘宝京东,他每天是不是有很多交易量啊?每天有很多交易量 是不是会产生很多交易日志啊?那这个文件是不是要保持很多份啊?是不是这样的?这个道理是一样的,那么这个就不再多说了,你看看你哪没写出来,或者哪有欠缺,你把我这个代码你看明白 可以改一改

后面讲的是线程 ,首先要了解他的基本概念,什么是线程,

线程 能够共享的资源,这些是不是都能够共享啊? 其实除了栈,除了每个线程里面,栈以外 是不是其他都可以共享啊?再有一个 那么你说这个如果说我这个进程里面有信号处理函数,那么这个线程能共享吗?是不是也可以呀?文件描述符,这个后面我们有这个文件描述符 后面会给你说的,会有相关的例子 咱们这个多线程版的服务器的时候呢,会用到这个这个多线程 共享一个文件描述符,每种信号的处理方式 这个是不是就是信号的处理函数啊?当前工作目录, 用户id和组id 内存地址空间 这些都是可以共享

这些是不是他自己的 这儿标红的errno 说不能共享这个变量,他的意思不是说别的线程不能访问它,而是说这个变量 让别的线程共享的话不安全,因为你一个线程里面改了,是不是另外一个线程也看出结果了?是不是,这个就不准确了,你不要用这个 咱们是不是有一个函数( sreor)函数是不是可以打错误号?对应的错误原因是不是打出来啊?

创建线程是不是用这个函数呀?pthread create 函数 相关的例子大家都写过一遍了没有?你协商几个就可以了,不用哪个都写,

当然这个图,循环创建多个子线程, 打印出每个线程是第几个来,这个原因 咱们一开始那个线程的分析以及 这个怎么解决的,这个都明白了吧?

解决的办法是,让每一个子线程 是不是访问不同的内存区域啊?这样的话可以解决这个问题了,

pthread exit 函数 线程退出

pthread_join 函数 等待回收子线程啊?如果不回收,是不是会产生僵尸线程啊?实际上按照专业术语来说呢,并没有僵尸线程这个说法,僵尸是不是只是说僵尸进程啊?但是呢 你这么理解就可以了,反正你不回收呢,反正 他就有一个(defakete)

pthread_detach 函数 意思是设置线程分离,设置线程分离之后呢,那么我们就可以不用在主线程里面调用pthread_join 回收他了,除了这种方法以外 还有另一种方法啊?另外一种方法是创建线程的时候去设置线程的分离属性啊?也可以,你用哪个方法都可以,其实我觉得呢,用在这个创建线程的时候指定更好一些,因为有可能你这个主线程设置还没有成功 子线程已经结束了,有没有可能?是有可能的

pthread cancel 函数 关于这一步呢,要求大家 知道一个概念,什么叫取消点,那么其实呢 如果你记不住的话也无所谓,是不是调用一个函数 设置一下就可以了?其实只要是阻塞的函数,或者是能进入到内核的函数都是有取消点的 比如说read write printf

 这个对照表你可以看一下,你结合着 对比来理解 

线程属性 咱们讲了一个  如何去设置线程的分离属性,步骤 1 2 3 很固定,这个你也不用记,记不住你找别人写的代码是不是一看就可以了?

关于线程同步的例子呢, 要求大家掌握 什么是线程同步,线程同步发生在什么情况下,那么多线程 访问共享资源 这个时候是不是需要使用线程同步啊?那么我们使用线程同步是不是使用的是互斥锁呀?这个锁什么意思 这个你得知道 ,其实我们使用互斥锁 相当于模拟了一个原子操作啊?那么一个线程如果说没有对共享资源操作完成 那么另外一个线程 就不能够操作 是不是?如果你不加锁会怎么样?咱们通过那个例子是不是看出来了?是不是数数的时候有可能会少数啊?虽然说我们试了10次 可能会有一次,但是这个现象也是存在的,

使用锁的步骤:

 

 这个死锁是linux 提供给我们的一种机制吗?是怎么造成的?是由于你写代码写的不当造成的,Linux并没有给我们提供这样的一种机制,要求你掌握读写锁的一种机制,读写锁是一把锁不是两把锁,那么这个读写锁可以做到什么样的效果呢?可以做到 读共享  写独占,特别适用于什么场合下呢?比如说我有10个读线程,五个写线程, 这个时候你使用这个读写锁 就比较合适,如果说你都是写,那么你使用读写锁是不是就不合适啊?他有他的场合 特别是这个读的这个场合 远远大于写的这个场合 这种情况下用读写锁效率高 因为读在一定程度上可以并行吧?但是互斥锁只能是串行啊?

熟练掌握条件变量的使用

在这呢给你讲一下 什么是条件变量 以及呢如何使用条件变量,咱们呢 在这呢条件变量这一块,我们在讲案例的时候呢我们用的是生成者和消费者模型 这个模型 大家上操作系统课的时候应该是学过,大学里面 是不是非计算机专业也学操作系统啊?计算机专业学这个

理解信号量实现的生产稍费者模型 这一块大家理解一下就可以了,用的不算太多

条件变量是咱们今天的一个重点 咱们今天呢,你只要把这个学会了,应该说你今天没白来

主要是条件变量,以及后面咱们讲多线程编程的时候呢 和线程池那个呢,用的就主要是条件变量,条件变量往往用的时候和谁一块使用啊?互斥锁一块来使用,因为这里面有一个加锁 解锁的操作

我把这个互斥锁的使用不走呢一块给大家说一下, 咱们要使用一把互斥锁 要使用锁的话呢,首先第一步 先定义一把锁,pthread mutex t mutex; 这一步是必须的吧?就像咱们前面讲 设置线程分离属性的时候呢,是不是第一步先定义一个属性变量啊?

初始化互斥锁,

pthread mutex init(&mutex); 初始化完成以后呢,就相当于mutex=1

接下来是不是要在这一个共享资源出现的位置要加锁和解锁啊?

 这个加锁和解锁的位置大家应该会了吧?不要搞错了,而且其实我们在加锁的时候呢 应该做的是力度尽可能的小,你不要把这个锁 加在这个毫不相干的代码上,这样的话会降低效率的,如果你加的话,如果你加在这个毫不相干的代码上,

加锁解锁 中间这个是不是临界区代码呀? 这个临界区代码的意思是 就是共享资源出现的位置,

那么这个锁用完之后不用了记得释放锁,那么释放锁在哪释放呢?是不是在主线程里面啊?这个是不是也是在主线程里面做的pthread mutex init(&mutex);? 定义的是不是一个全局变量啊?为什么呀?因为你这两个线程当中是不是都要用到它呀?

我在这 给你咱们找了一个这样的例子,这个例子看起来比较明显一些,加锁和不加锁区别很大,不像 我们数数 有的时候试半天试不出来啊?

看这个代码 咱们先从主函数看一看,组函数是差不多的, 是不是一两个线程变量啊?然后创建两个线程

 

 当然这里面是不是有一个锁的初始化啊?当然最前面是不是肯定是 定义了吧?接下来咱们看 主要是看两个线程,那么定义线程里面 我们打印的也是在一个while循环里面不停的打印啊?19-21

 我问一下大家 我为什么加了一个20这玩意?目的是什么?是不是我加sleep的这个目的是让这两个线程之间来回切换啊?切换的概率是不是更高一些啊?如果你不加这句话的话,这两个hello world 是不是一块打印出来了?这种概率就更高吧?我加这个sleep原因是什么?就是让这个printf 打印完了他sleep他就会让出cpu 啊?是不是线程2 他就 得到了,有可能他就把这句话打印出来hello,有可能他把这句话打印出来了world 是不是都有可能啊?知道什么意思了吗?你要搞清楚我这样做的目的是什么

 然后这是我不加锁的情况下,不加锁的情况下 那么你说这个helloworld 是不是打在一行的可能性很低啊?同理这边是不是也是这样的?有可能会打印乱了 比如说hello WORLD 然后这个HELLO world 是不是都有可能啊?就乱套了,能想出来吗? 那么我给你演示一下是不是这样的?

是不是乱套了?我们的目的是不是应该看到 hello world  在一行啊? 大小写各在一行 这样才好,很显然 我们这样做呢, 是不是就乱套了? 怎么解决呢? 要加互斥锁 ,那么加互斥锁以后 那么你看一下,我把这个锁给你打开,

 

放开锁以后大家注意, 我问一下大家,假如说我执行到20这个sleep这句代码了,他肯定是不是就会让出cpu啊?让出cpu 这边能执行吗38?为什么呢?是不是他线程1没有释放锁呢  线程2 他也得不到这把锁,得不到这把锁 这个cpu在切换回来的时候是不是从这继续往下执行啊20? 大家注意这个sleep就结束了,继续往下走吧?结束 他释放锁以后,线程2 37 他是不是有可能就得到锁了?也有可能得不到啊?有可能在一个cpu时间片内 他是不是循环执行多次啊?这是有可能的 但是这一部分你加上sleep 之后呢20,这种可能是不是就没有了?因为一个cpu时间片 不可能说好几秒钟的吧?这一种肯定不会,这种肯定是多个cpu时间片 是不是才会执行完一个操作啊?当然你把这个时间弄小 20,他是不是就有可能了?就不太明显了,我给你演示的是如何明显的看出效果

那么咱们加完锁之后 ,咱们再看一下效果

 通过这个例子 是不是我们也看到这个加互斥锁的一个效果了吧?如果你不加怎么样?加了怎么样?前后一对比是不是很明显啊?这个互斥锁的使用 要求大家务必掌握,应该说掌握起来并不难吧?

注意: 必须在所有操作共享资源的线程上都加上锁否则不能起到同步的效果。那么你这两个线程 你只加一个锁,对另外一个线程你不加锁是不是相当于把钥匙给他了?还能锁住吗?这个相当于防贼 防不住吧?

关于互斥锁 就说这么多,例子自己看

肯定是这两个子线程回切换啊?加了锁之后呢  他就什么呀?你这个操作 即便是你在这里面等再长的时间,他是不是只要不放锁 他就得不到啊 ?同理 这边是不是也是这样啊?

 死锁不是系统提供给你的一种机制,而是由于你的操作不当引起的,主要是由于编码不当引起的吧?咱们给大家介绍两种最常见的死锁,这两种死锁 间隔的比较常见 ,比较多一些 你理解一下 看第一种,

第一种:自己锁自己,如下图代码片段

自己锁自己 是不是最常见的就是锁两次啊?还有一种 你这个加了锁不释放 是不是也会产生死锁啊?你加了锁不释放,另外一个是不是加锁不成功啊? 这种情况是比较简单的一种死锁,咱们看一下 我们的代码里面第一次加锁了 后面他又加锁,很显然,第二次加肯定不成功吧? 这第二个是不是他把自己锁住了?

我给大家演示一下是不是这样的?

 

是不是把这一行代码复制一下就可以了17?  死了吧? 不动了,锁哪了 是不是锁18行了? 我问一下大家 有没有可能像这样 像printf 一个也输出不出来啊?如果先执行这个线程 线程1 是不是一句都输出不了啊?当然那个是先跑的线程1吧? 先跑的是第二个线程

一次输出不了, 你看我们这个 两个线程 谁先执行谁后 执行 是不是不确定啊?这个是一种死锁, 

我再给你模拟一种死锁  也是一样 类似的,

我把这个删掉 会锁在什么地方啊? 有两种可能  一种是锁在17, 如果你不加这个sleep的话 他有可能会锁在17 这啊?

一种是锁在37这,这两个锁在两个线程上是不是都有可能啊?比如说 他加锁成功 执行完以后呢, 他又循环一次,又开始执行,执行到17就锁住了,那么还有一种可能就是他执行到这个位置20,他是不是又让出cpu了?让出cpu 是不是线程2执行啊? 线程2 在37锁住了,都有可能 那么我们这个的话,大家看一下 如果我这样写的话,有没有可能一句话也打印不出来啊?那么一句话打印不出来的可能没有,为什么呀? 先看分析这种,如果他19先获得cpu 这句话是不是肯定会打印出来呀?如果他先获得cpu 39-41这句话是不是也会打印出来呀?所以这个呢,就不会出现那种一句话也打印不出来的情况了吧?

这一打两次 ,是合理的吧?  线程2先执行就会打两次,然后他加锁成功  把这句话打印出来,接下来死锁了吧?无论先执行到他17也好,还是无论执行到37也好,都会产生死锁,这个死锁是不是比较明显啊?还有一种情况,假如说你的线程 子线程1,由于异常情况下退出了,他是不是也得需要解锁啊?如果不解锁 别的线程  能得到锁吗? 是不是得不到锁?比如说你在这malooc 内存空间,然后这个malooc失败了,malooc失败你这个线程 是不是需要退出啊?你这个退出的话,你要解锁,只要是你加的锁,你退出之前一定要解锁,这是必须的,咱们看另外一种情况,

那么这种情况 就需要大家理解一下了, 第二种情况是这样的,那么线程A呢,拥有A锁,线程B拥有B锁,现在线程都有自己的一把锁啊?线程B拥有B锁,请求获得A锁 那么我问一下大家,如果说线程A不释放锁,他能获得锁吗?这儿是不是参生死锁了?那么同理线程A现在他拥有A锁的同时呢,他也想请求B锁,是不是这个B锁 被B线程占用了?是不是他不释放,那么别的线程是不是获得不了这把锁啊?这个是不是也产生死锁了?这种是什么情况?是不是两个线程交叉住了?是不是A线程的锁被线程去使用,他是不是得不到这把锁啊?B线程想要获得A的锁,由于 他A呢,已经把这个锁占用了,是不是B也得不到啊?这两个线程是不是交叉一起了?谁也得不到对方的锁,而且谁也不想释放自己的锁,是不是光想拿不想释放啊?这有点类似于( 广占网不吃亏),这种情况 其实现实生活中也遇到过这个问题,我不知道大家去政府部门办事的时候,有没有遇到这样的情况,比如说你去开证明,也就是说你得先开一个证明,他告诉你了,你先开那个 你拿过来之后 我再给你开,你跑那去了是不是不行,你得先开那个,两个是不是就咬住了?结果呢 谁都不给你开,这个时候呢 解决问题的方法只有一种, 我一个同学遇到这样的一个问题,他想把户口迁到山东去,那这边呢,不开材料 那边呢拿不出材料不给签,后来没办法跑了两次都不给解决,解决不了,他说你怎么这样啊?你这样吧,我把那边的工作人员的电话给你,你们俩说,你们俩沟通,后来他们协商一下 把这个问题给解决了,反正跑了两次都解决不了,那么你说你这样干行,他说他这样干行?你们俩协商一下吧,看谁到底先松口,是不是

这个图就形象说明了,我刚刚给大家讲的这个, 线程A呢?是不是拥有A锁啊?他是不是已经把这个A锁加载成功了?然后线程A他正在访问共享资源A的同时呢,他又想去访问共享资源B 有没有可能?那么他访问B的时候, 一看这个锁被占用着呢, 他是不是就lock不成功啊?加锁不成功,他是不是需要阻塞等待啊?

线程B呢,他正在占用这个B锁吧?同时呢 他也想去获得A锁,那试问一下  那么这个A锁是不是已经被A占用了?那么你说这个B 还能够加A锁成功吗?显然不能成功吧?怎么办?他是不是也阻塞啊?这个时候呢 就产生死锁了,那么这样的话,占用A锁的同时 想请求B锁,得不到,B在占用B锁的同时,想请求A锁,他也得不到,是不是相当于 A 占用了B 想要的锁,B占用了A想用的锁,这样的话 他俩交织在一起了,是不是谁也不释放锁,这样的话是不是产生死锁了?那么问一下大家 这种情况怎么解决?其实最简单的办法是这样的,你线程A 你想要访问线程B的时候呢,优先把A锁释放,那么B呢 你想要访问A锁的时候呢,你先把B锁释放,那么这样的话呢,你每次只占用一个锁肯定,不会出现这种情况,再有一个 我们使用锁的时候呢,我们尽可能的不要嵌套, 大锁里面加小锁 小锁里面还加锁,好几层, 很容易产生死锁,那么其实你也想一下,这个锁你加的越多 是不是线程越慢啊?是不是因为你一旦加锁之后,是不是就变成(..) 不论是你有几个cpu 几个核,一定是这样的,因为这个锁是不是只有一把呀?谁得到锁 谁就执行,谁得不到锁谁就阻塞等待,

如何解决死锁呢?

让线程按照一定的顺序去访问共享资源  什么意思呢?你访问A的时候 你把A访问完了 或者是你不访问了,你释放之后你再去访问B,B现在是不是也一样的?你访问B的时候呢,你这个时候你就先不要访问A呢,那你把B释放了以后,你再去访问A 也就是说呢,你不要吃着碗里的,占着锅里的,

在访问其他锁的时候,需要先将自己的锁解开  也就是说 你同时只能有一把锁啊?同时不能占据两把锁,

调用 pthread mutex trylock,如果加锁不成功会立刻返回  尝试加锁 他不会阻塞,如果加锁不成功 是不是他会立刻返回啊?那么这种情况下 也不会产生死锁,这个是说给大家提供了一种解决方法  那么实际上,在你写代码的时候  你只要保证 你不同时占据两把锁 或者是 不同时 有那种嵌套的那种加锁情况 ,那么一般死锁 就不会产生,异常退出 你的线程 也需要解锁,只要你加锁了,就意味着 你一定要解锁 ,不要搞混了,

1.互斥量(也叫互斥锁)

2.死锁:

1 自记锁自己
注意点: 线程在异常退出的时候也需要解锁

也就是说只要是你加的锁,你不用了 要记得释放锁,是不是这样的?

2.A线程 占用着A锁,又想去获得B锁, 那B线程 占用着B锁,又想去获得A锁,两个线程都不释放自己的锁,又想去获得对方的锁,从而造成了死锁

 解决方法:

   1.如果你想去占用另一把锁的话是不是先释放自己的锁,再去获得其他锁,

   2.避免使用嵌套的锁,不要说锁里面还有锁  这样的话很容易造成死锁,而且你是不是不易控制啊?一旦锁住了 没有任何输入你是看不到效果的,你想用日志control你control不到,他不往下走嘛,还有呢,让线程 按照一定的顺序加锁 我用完这个锁了我释放 释放之后呢,我再去获得另外一个锁 这是可以的,

   3.可以调用哪个函数呢?pthread mutex trylock刚数加锁 这个函数是不阻塞的吧?该函数不阻塞,所以不会产生死锁吧?其实你这个函数只要不阻塞 他是不是肯定不会产生死锁啊?

关于死锁 大家了解到这里就够了  死锁 不是linux 提供给开发者的一种机制,而是由于开发者操作不当引起的

 

读写锁,也叫共享独占锁,那么什么情况下共享的呢? 那么在读的时候呢,读操作是共享的, 那么什么是独占呢?写操作的时候是独占的,所以呢他也叫读写锁,当读写锁以读模式锁住时,它是以共享模式锁住的,那么其他的读线程是不是还可以操作这个共享内存啊?说白了 就是可以读吧? 当它以写模式锁住时,它是以独占模式锁住的。写独占、读共享。 红色标记的你重点看

读写锁使用场合
读写锁非常适合于对数据结构读的次数远大于写的情况。 这个使用场合其实也很容易理解,那么我们使用读写锁的目的是不是在一定的程度上 让他最大限度的提高并发性啊?或者并行 是不是 类似的  那么什么意思呢?就是说 如果说 咱们这个线程里面 这一组线程里面,那么读的线程数量 要大于写线程数量的时候呢,言外之意是什么呢?你读的时候  比写的时候多,这个时候 你使用读写锁的效率高,那否则的话 你基本上都是写操作,你使用这个读写锁和使用互斥锁是一样的,读写锁的特性呢,咱们就一块说一下就可以了,

看第一个

读写锁是“写模式加锁”时,解锁前,所有对该锁加锁的线程都会被阻寒。 这句话是不是体现一句话来呀?哪句话?是不是写是独占啊?只要是你写线程 把这个锁加住了,把这个写加锁成功了,那么其他线程 都需要阻塞,那么这个也体现出 写是独占的,

读写锁是“读模式加锁”时,如果线程以读模式对其加锁会成功;如果线程以写模式加锁会阻寒。

是不是读共享啊?如果说你一个线程加锁,加读锁成功了,其他的读相当于也过来了,是可以加读锁成功的,那么能不能加写锁也成功呢?不行,读写是互斥的,

如果线程以写模式加锁会阻寒。 那么什么情况下 他就会解除阻塞啊?这个读释放之后 是不是他就加载成功了?

读写锁是“读模式加锁”时, 既有试图以写模式加锁的线程,那么也就是说

这个时候 是不是堵着呢,r  他已经加锁成功了, 这个时候同时来两个线程 一个是写线程,一个是读线程 ,这两个线程都会阻塞,那么假如说 这个时候呢 你这个r 这个时候退出了,释放锁了吧? 他俩谁先获得锁啊? 写,这体现出一句话,读写锁 共同在等待锁的同时 如果说 这个锁释放了,优先获得锁的是谁?写线程 

那么写线程 是不是只是获得写的锁啊?读线程 只是获得读锁,

 这个是读写锁给大家出了个练习题,咱们练习一下,其实就是把那三句话是不是理解一下啊? 哪三句话呀?

写是独占的  读共享,然后若读和写共同在抢锁的时候呢,写的优先级高 我再问一下大家,这个读写的时候到底是几把锁?这个务必搞清楚,别看这字面上 读写各一把锁 其实不是这样的,读写的时候只是一把锁,

 线程A加写锁成功,线程B请求读锁 那么线程A已经加锁成功了吧? 那么线程B是不是请求读锁啊? 这个时候 线程B阻塞? 当线程A解锁之后呢, 线程B加锁成功

线程A持有读锁,线程B请求写锁

线程A已经占用一把锁了,那么线程B会加写锁成功吗?不会,读写是不是给你说过了?是互斥的,读写锁 读锁 和写 这俩只能同时有一个在操作吧 线程B会阻塞; 当线程A解锁之后,线程B加锁成功

线程A拥有读锁,线程B请求读锁  线程B请求锁成功 是不是不阻塞啊? 不阻塞

线程A持有读锁,然后线程B请求写锁,然后线程c请求读锁 这句话的意思,这个是不是相当于A已经把这个锁加上了?已经加好了,那么这个时候呢线程B和线程C 这个时候要请求锁呀?首先看怎么说的?

第一步   线程B 和C都阻塞; 当A释放锁以后呢,B先获得锁,C阻塞 

当B释放锁之后呢? C获得锁

 线程A持有写锁,然后线程B请求读锁,然后线程c请求写锁

 线程A持有写锁,这句话的意思是不是他已经加锁成功了?后面是不是有两个线程在等待锁呀?

线程B和C都阻塞吧?当线程A解锁之后,C先获得锁,B阻塞  当C解锁之后呢 B获得锁

 整个这个作业题就可以把咱们这一句话给你叙述一遍啊?给你解释了一遍, 写独占 那么你在这个加写锁成功是不是其他下面都会阻塞啊?你不管后面有几个线程 是不是都阻塞呀?

读共享 你一个线程 加读锁成功了 又来了好几个读线程,这个时候是不是这几个线程都可以加读锁成功啊?

那么当读和写 一起等待锁的时候呢,写的优先级高 那么这句话的意思 是A线程 我先加锁成功了,大家注意,不管什么锁,读锁也好,写锁也好,他只要是这个锁一开始被占用了,后面的这个线程只要有写操作,那么其他的读线程就得不到这把锁,是这样的吗?那当然A线程解锁成功了,是不是其他线程里面,在这个其他线程里面是不是写线程先获得锁呀?那么这个写锁释放之后,其他的读线程是不是就可以获得锁了?整体上是这么一个过程,读写锁这一块就说这么多,

这基本的概念,

写独占,读共享,当读和写一起等待锁的时候,写的优先级高 把这句话理解了就可以了

 使用读写锁 相关的主要函数,第一个 也是一样 和互斥锁类似,先定义一把锁,接下来 定义一把锁之后 是不是 在我们互斥锁里面,接下来 要进行初始化呀?这也是一样的,

int pthread rwlock init( 进行初始化, 第一个是不是咱们的锁呀? rwlock, 是不是这一步定义一把锁啊?第二步是一个属性,这儿你传null就够用了,默认属性就可以了,

第三个 这个锁你不用来,记得释放吧int pthread rwlock destroy 这个和互斥锁是不是也一样?

这个读写锁到底加在哪呢? 跟那个互斥量完全一样,是不是加在共享资源出现的位置啊?

咱们看一下这几个函数,第一个是destroy 这个没得说吧?

第二个int pthread rwlock rdlock 这个是不是相当于读锁啊? 这个是加读锁

下面是加写锁int pthread rwlock tryrdlock(是不是和咱们前面讲过的  trylock是不是类似的呀?这个是不是也是不阻塞的呀?他可以尝试加锁,如果加锁不成功呢?加锁成功和加锁不成功是不是都返回呀?到底有没有成功,看返回值

后面这个int pthread rwlock wrlock 是不是写锁啊?你看一下这个锁是同一个,但是呢是不是加锁和加锁的函数不一样啊?加读锁有加读锁的函数, 加写锁有加写锁的函数,但是呢,用的这个锁 是不是都是这一个啊?

这有点类似于什么呀?这有点类似于这个东西 这个同一把锁是不是有两个不同的钥匙?

这个是尝试加写锁啊?

这个锁不用了,是不是要解锁呀?解锁他是不是都是一个啊? int pthread rwlock unlock

这个有点类似于,这个怎么理解呀?就是你这个锁 他是一种特殊的锁,那么你开的时候呢,可以同一个要是开,但是你用这个钥匙 有时候这个锁,需要用这个钥匙来锁呀?大家记得吧 这个车钥匙一般要用这个钥匙来锁的,开可以是另外一个钥匙,锁呢,只能是两个钥匙,加锁一个 是不是解锁一个呀?加读锁一个 加写锁一个 他俩是不一样的,其实操作的锁 是不是同一个呀?这个使用步骤 和互斥锁是一样的,咱们这儿给你说一下,让你回顾一下那个,

第一步先定义一把读写锁:pthread rwlock t rwlock;

第二步 初始化 在主函数里面  初始化 读写锁

pthread rwlock init(&rwlock, NULL);

第三步 接下来 你是不是要找共享资源出现的位置吧?那么你看共享资源出现的这个线程的代码哪个地方?是不是共享代码上下加锁就可以了,加锁 找这个加锁的位置呢,和互斥锁一模一样,加锁的话怎么加锁?

pthread rwlock rdlock(&rwlock); 这个加的是读锁

pthread rwlock wrlock(&rwlock);--加写锁

第四步pthread rwlock unlock(&rwlock)  这个解锁都是一个函数,

中间出现的位置是什么?共享资源出现的位置

 这个和咱们讲的互斥锁是一模一样的,只是调用的函数有所区别,但是原理都是一样的,

最后一步 释放锁

读写锁 这几个函数介绍,以及这几个函数 使用步骤咱们就说这么多,下面咱们通过一个案例带大家来用一下,那么举例子的时候 咱们怎么举呢?肯定是先使用读写锁嘛, 你肯定是读的概率是不是要高于写的概率呀?那么咱们就这样,比如说创建5个子线程 你用3个读线程, 两个写线程 是不是可以呀?或者你创建10个 比如说6个用于读 然后4个用于写 是不是也可以呀?总之 你那个举例子的时候呢 要尽可能 模拟场合呢 要和这个 他的使用场合是不是要一致呀?你不能你定义的时候呢,你这个读线程有两个  你这个写线程 有10个,那你还不如 使用互斥锁呢,

写个例子 这个例子很简单,你只要会互斥锁 这个你就会,其实现在 我让你写 你们也能写得出来,

vi 01-pthread_rwlock.c

读写锁 测试程序 怎么个要求呢?

3 个线程不定时写同一全局资源,5 个线程不定时读同一全局资源。

是不是相当于 3个写线程 5个读线程啊?是不是这个读线程的数量要多于写线程啊?那么我们是应该是让这5个读线程是不是可以并行操作呀?这3个是独占的, 这5个是并行的, 这样的话是不是我们的进程,我们的程序里面是不是可以实现 一定程度的并行啊?比那个串行是不是要效率高一些呀?

首先咱们是不是要创建,一共要创建几个线程啊?一共创建8个,3个写线程, 5个读线程 这个创建子线程  int n =8;

前三个是写线程,然后是不是你得定义8个这样的一个变量吧? 现在能这么写吗?pthread t thread[n]; 有的时候能 但是有的时候 试的也可以 这跟编译器有关系,你自己试

是不是写线程啊?相当于  后面是参数吧?咱们把这个第几个线程打出来可以吧? 应该定义一个变量吧? 这一个和咱们前面讲的是不是都一样啊?

 这样的话是不是就创建几个了?是不是3个了吧? 这是三个写线程

 咱们把这几句话复制一下就可以了,这个是不是相当于创建5个读线程吧?

然后咱们回收一下,回收的目的是不是可以让我们这个进程退出啊?回收子线程, 循环回收,为什么这个用j啊?别混了 ,

 

大家注意 在主函数里面调用return 0 是不是和调用那个 exit 是不是一个意思?但是在函数里面 这俩有区别呀?

接下来是不是我们的主程序写完了

接下来是不是开始写 线程回调函数了?

这个是void * thread_write(void *arg) 首先我们把这个参数接收一下呀?

int i =  *(int arg)  ; 这个是不是先做强制类型转换啊?转换完以后 这个是不是相当于指针了就?

一个int形的指针,再加上* 是不是相当于取值的意思啊? 这个是不是叫间接引用吧? 这个有了,接下来咱们就开始数数了呗 开始写了呗,当然 我们还得定义全局变量吧? int number =0;一点点来 咱们先不加锁呢, 别着急,你写的太开了 搞不好 乱套了,思路就不清晰了,一点点来, 接下来咱们是不是需要开始数数了? 开始定义一个工具变量吧? cur =number;cur ++;number = cur;  为了避免他数得太快,我们应该干什么呀? 我在这儿加一个usleep(500)是不是很容易看出来了?500是半秒钟的意思,

 usleep(1000)=sleep(1)

接下来 这个相当于写操作,相当于做了++操作,接下来在子线程里面 咱们做的操作是什么样的呢?

这个是读线程回调函数,回调函数和执行函数 在这里是不是一个意思啊?

直接取值打印就可以了,

 这是第几个写线程 这是第几个读线程 我们打印出来的是不是cur啊?咱们就实验一下 按理说 咱们看一下 这个程序里面 咱们是不是,3个线程写,5个线程读啊?如果说正常情况下 是不是这样的?你读的数据 应该是最后写入的吧?你读的数据的值,应该是你最后写入的一个数据的值,为什么呀?假如说我举个例子,假如说一开始你一开始你读了,后来又来个读 又来个写,你这个时候肯定是先写再读吧?所以说你这个读的数据一定是上面最近的写入的数据,这儿 为了避免他的这个读的太快 咱们应该加个usleep(400)

没跑起来呀,怎么立刻就死掉了呢?

回收子线程 j<n; 你这个i已经达到8了,这个条件满足 是不是立刻就退出了?这个条件满足是不是一直进不来呀?

看一下 咱们看一下 这个肯定会出现有问题的。

 [0]-w:[607]这个是不是读呀?  [2]-w:[607] 这俩是不是重了?

再看一下这个读 [4]-r:[608]   [1]-w:[606] 这儿是不是又来个606啊 很显然不合理吧? 你上面的数字 肯定比下面的小吧?要么相等,相等应该是 读的这个有相等的,

但是写的话,上面写的一定是比下面的小,下面写的大,很显然这儿是不是出问题了, [3]-r:[608]

这儿是 [1]-w:[606] 这儿是[2]-w:[608] 没问题 当然这个问题肯定是按照我们的想法肯定是存在的吧?那么我们接下来是不是加锁了?加锁之后这个问题就得到解决了,咱们就看一下加锁 这个怎么加锁,按照咱们的步骤来,

第一步先干什么呀?

先定义一把读写锁,pthread_rwlock_t rwlock;

第二步要读写锁初始化,第一个参数是那把锁,第二个参数是NULL

这个锁不用了 要释放锁,

 

接下来是不是要 开始找这个加锁的位置吧?在哪加呢?是不是应该加在共享资源出现的位置呀?

在这很明显,我这个锁应该加在while 1 上面 或者 while{} 后面啊?为什么呀?你这个一加不得了,另外一个线程 肯定得不到锁了,是这样的吧?

 这个写锁 咱们直接给他复制过来,这个写锁 是不是后面是一样的?那你说我这个写锁是加在sleep之上 还是这个sleep之后?是加在usleep的上面 还是下面?仔细想一下 加上下是有区别的,为什么加上面?加下面慢,你这个usleep无端的等待,你加在 usleep的下面 是不是意味着你这个400过后 别人才能得到这把锁啊?这样的话 是不是让别人等待一段没有用的时间 因为你确实没做事吧?要知道这个是为什么,

 加锁函数不一样,解锁函数都一样 那么如果这样的话,我这个28 sleep 这个放到这是不是也不太合理了?这个是不是也要往下移啊?是不是?我加这个sleep的目的 其实是让刚才让那个现象 更容易出现,在这我们加锁之后 是不是 应该让他们进程快呀?不能说你这个usleep在这,相当于无端的等待呀?没有任何的意义,这句话是不是要写下来呀33这才合理吧?你现在加锁 加了锁之后肯定不会引起数据错乱了?快点就快点嘛,无所谓了,肯定是这样的,试一下 是不是
先看写

 这一块没问题吧?读的是你看是最后写入的? 7 3 6 5 这四个是并行读的? 读的都是同一个数据嘛,再看这个写[0]-w:[2573]  [4]-R:[2573]这个读的是不是也是最后一次写入的?

[3]-R:[2574]  [6]-R:[2574] 这几个都是,然后这样 咱们这个时间呢,咱们都是固定的,咱们给他来个不固定的,这个usleep 是固定的时间吧  怎么给他来个不固定的时间?来个随机值

 

 rand 是取一个随机值 只有3个值 0 1 2,再试 刚才这个肯定是比原来的慢了呀?
那么你看一下这个读 7 7 7 7是不是有的时候  这一个线程是不是读好多次啊?
再看一下这个4  是不是读2次啊?这一块是不是读操作都是没问题的吧?读操作 是不是正是写操作的最后一次数据啊[0]-w:[28]
再观察一下,到这个写 写最后一次是不是 31啊?[1]-w:[31] 这几个线程 5 6 3 是不是来回的 你看一下这几个读线程里面,你看他执行的是不是这样的 是不是有的线程 有的时候得不到组的执行啊?这儿是不是正好验证了,cpu的时间片是随机的呀?而且这个cpu的时间段 他的长短也不固定,有的时候长 有的时候短,这一个也不是你考虑的问题,反正你知道有这么一回事就可以了,这个31读的正是最后一次写入的数据吧?

 再观察一个,看这个[1]-w:[38] 这个第一号进程的最后是不是他写入的数据是38呀?后面接下来这几次读操作是不是 都是他?咱们的代码没有问题,这个咱们就说到这 

咱们今天讲这个条型变量,在讲这个条形变量之前呢,我先给大家讲一下这个链表操作,因为咱们这一块要用这个链表来模拟生产者和消费者,那么所谓的生产者是不是要往里面加节点啊?消费者是从读出数据是不是要释放这个节点啊?用的就是链表操作,链表操作大家还熟悉吗?咱们定义一个这样的链表  什么链表呢?
这个是不是一个节点啊?咱们原来大家讲链表的时候 这个链表的头节点是不是 是一个空节点啊?在这我们咱们讲的这个节点 这个节点是一个有节点 有实质的内容,这个你随便 这个你看你的需求,比如说 我们定义一个变量 
定义一个结构体
Typedef struct node{
int data;
struct node *next;
}NODE;

你这个一般的话,我们要写这个定义一个链表的话,是不是都是这个malloc出来的一块内存啊?把这些内存 是不是接在一块 就成为了一个链表啊?而且我们一般是用一个结构体吧?
struct node *next;一个指针
起一个别名叫NODE
前面要写Typedef
这是一个节点,那么我们要操作这个链表的话是不是应该,你得记录一个头节点啊?
我们这个头节点,我们定义为什么呢?NODE * head =NULL;初始值 一开始没有
那么咱们看一下这个操作,第一步 你要 是不是要讲这个head 指向一个头节点啊?很显然这个头节点 是不是一开始没有啊?应该怎么办啊?是不是应该生产出来一个头节点啊?  
怎么生产?是不是应该是head 
当然你这个时候不要用head 这个head 是不是应该最后要保存起来呀?也应该用一个中间变量来回的指向啊?移动指针就可以了, 比如说我们叫NODE *pNode =NULL;链表操作是大家工作中用的比较多的一个结构,这个你务必要掌握,初始化都为空,指针嘛
接下来你要生产一个节点的话,那么这个生产一个节点谁来做的呢?在我们这我们把他称之为 生产者 其实呢 在我们程序里面表现为一个线程 叫生产者线程,那么还有一个是不是要读这个链表的数据呀?他读完之后 释放,这个我们称之为什么呀?消费者线程,意思明白了没有?那么接下来咱们后续的操作 要在生产者 和消费者 这两个代码里面 这两个线程里面要加什么呀? 要使用互斥锁, 大家想一下 为什么要使用互斥锁啊?为什么呀?那么你这个生产者 消费者 你读的都是这个head指向的链表啊?这个链表是不是一个共享资源啊?那涉及到共享资源的操作 是不是要使用互斥锁,因为他要互斥操作嘛,

咱们接下来是不是要生产一个节点啊?
pNode = (NODE *) malloc(sizeof(NODE));当然判断我就不写了,你得判断一下吧?是否是成功是否是失败,相当于我们生产了一个节点吧?那么生产了一个节点,那么这个节点一开始的话 是不是应该被head指向?
head =pNode;
接下来我给大家说一下,如何进行插入,现在我这个head 已经指向第一个节点了?

执行插入操作,这个操作 一共分为几类?
插入 删除吧?
当然这个链表常见的操作 插入的操作,删除操作,遍历操作 是不是就这几类呀?一般也就这几类,
链表插入操作
很显然 链表插入操作 第一步 首先是不是我们也先生产一个节点出来呀?那么生产一个节点出来以后,接下来呢, 大家注意,一开始 我们这个head是不是有了节点了? 因为你已经生产出来一个了吗
其实这个插入操作 这个是可以合在一步,
你一开始 是不是head =NULL  是不是初始化等于空啊?

怎么办?没有的话 你是不是可以让 head =这个
head = (NODE *) malloc(sizeof(NODE)); 这个新节点吧?
一个节点了吧?都行
这样可以吗?
这是什么呀?你可以加一个判断吧?
如果 flag=1 这个你随便都行 总之你把这个事完成就可以了,是不是相当于head =1 先flag=1 初始值就是1啊?
是不是你应该head 先指向一个有效的节点啊? 后续的话 else 后续 需要这个pNode 相当于他要再申请一块内存出来呀?
这个时候 是不是pNode 已经有节点了?接下来是不是应该把这个pNode 往这个head 前面插入啊?怎么插入我们用头插法 这个是不是 node , head 是不是有了?

又来个节点,这是不是一个节点啊?这是不是一开始head 申请出的节点啊?

那么接下来怎么拼接在一块啊?
是不是先赋值啊?你得对这个节点赋值啊?
比如说pNode->data = rand()%1000;是不是他取0-999啊?那么这样的话 这个节点是不是有值了?随便写一个 pNode =10
接下来是不是要拼接上啊?怎么拼接?

这一步其实你可以去掉,咱们就合一块写 你看怎么写,
第一步 这个head 先放一边去,一开始=NULL;
一开始我是不是先 pNode =一块内存空间啊?
这个绿方框 指的是 PNode
接下来你怎么着让这个head 要给他接上去啊?
你是怎么写?是不是
pNode =head->next;
咱们用头插法吧,头插法简单
如果你用尾插法的话 是不是应该用这个pNode->next;啊?是不是往下走啊?
咱们使用头插法,这个头插法他一样吗?道理是一样的,只不过是一个往前拼 一个往后拼吧?最终 这样的话是不是都是你创建出节点啊?
首先 pNode 是不是应该= hand->next;
我问一下大家  第一次 head 是不是=NULL啊?这样写行吗?
pNode= hand->next;
应该怎么写?你要么你麻烦一些 第一次你先判断一下第一次 这个第一次 head 肯定为=NULL;head为NULL的话 你指向第一个节点,
如果不是第一次 ,是不是后续的往后接啊?也行,总之这个意思 大家明白就可以了,那么操作方法也不止一种, 这个pNode 是不是应该指向head->next;啊?前提是什么?第一次你已经创建一个节点了吧?如果第一次没有节点呢?

pNode 是不是 =head啊?你搞清楚啊,咱们用头插法  
尾插法是不是应该是head->next =pNode啊?
头插法怎么写啊?pNode= head;
pNode->next是不是相当于 这个相当于拼接上了,这个链表操作 你们得慢慢熟悉起来,是不是相当于这样啊?👇

 接下来你这个head 是不是应该是=pNode 啊?你这个head 是不是指向这个了?你已经接上了 ,你注意已经接上了,
在这儿 头是动的,大家想一下,我给大家举个例子,我这个生产者和消费者 我这个共享的是什么东西?共享的是不是头?因为这个头进来是不是指向的整个链表啊?我们这个头 你消费者用的时候也是用的这个头,头这个结尾  这个头往后移,都一样 你是尾插法 都是一个意思,最终是不是我们需要把这个链串起来呀?

pNode->next =head;  

接下来,head 往前移 head = pNode;

这个相当于生产者

 待会咱们注意一下, 看一下这个能不能合在一起,一开始有head  第一个head 是没有节点的吧?

pNode = (NODE *) malloc(sizeof(NODE));这一块是不是相当于申请一块新的节点啊?
pNode->data = rand()%1000这个是相当于赋值,

pNode->next =head;  
这个flag 其实是可以删掉的, 能不能删? 现在这个head 是不是=NULL?
我这一步是不是申请一块内存出来了?

第一步有了,
第二步 我赋值不用管
第三个 pNode->next =head;   一开始为空, 也就是说pNode->next 之前为NULL 没有值,head =pNode 是不是这个head=他了
是不是相当于 第一个节点有了?
能不能去掉?
这两步是可以合成一步的

 

这个没有关系,你写代码的时候,我写的过程当中一开始没有发现问题,你不用管他 你先写 那么你写着写着 你就发现了,写下来改嘛,这有什么呀?
谁写代码 一次喝成啊?
这个逻辑正常应该来说写在while语句循环里面?是不是应该是不停的插入啊?
谁调生产者吗?
当然你这个东西不能太快了呀?太快内存有可能会耗尽,你如果不加sleep1 有可能会使内存耗尽,因为你这个循环太快了,一直申请不释放,有没有可能? 看看消费者那一块怎么去消费的,那么所谓的消费 其实也很简单,就是读链表的值 然后读完之后 释放掉就可以了 👆

刚开始我们申请的是一个节点,那么再来一个节点呢?

我把这个东西 再给你模拟一遍 看看是不是这样的,这个相当于 pNode 申请出一个节点啊?
这个是head  这个是pNode 接下来又开始拼接了,我说用什么法呀?
是不是头插法?用头插啊? 现在的 节点是不是 只有一个head 头节点啊?接下来再插怎么插?第一步
不用说有了吧?这个是pNode->next=head;
然后第二步

head =pNode  是不是这个要迁移,接下来 我这个head 仍然指向这个头节点啊?看这个箭头指向。这个是不是相当于第二个节点啊?你无论是使用头插还是尾插,怎么操作简单 怎么来,而且你还能达到一样的功能吧?大家想一下,这个链表比这一个数组方便 方便在哪呀?

 

 是不是根据需要来开辟呀?你数组的话是不是得写死啊?我这里呢,我根据需要,我有多少我要多少,而且我用完是不是要释放啊?动态可以调节大小,这个就是刚才那个pNode吧?
再来一个呢?再来一个是不是给这个类似的呀?
如果再来一个 是不是相当于又来一个呀?你写代码的时候 如果说这个代码呢,比如你拿不准,那你就去在纸上画一画模拟模拟,那么接下来 pNode 又申请一个新的节点,然后pNode->next =head;
然后这一节又拼上了,然后head 往前移啊? head =pNode

这个pNode是不是就没有了? 现在是不是head 仍然指向第一个节点啊?
都行,你写的时候 这一块没有什么强制性的要求,总之 你只要把这个意思弄明白就可以,这个生产者  这个链表插入就相当于生产了,咱们先把这个链表一块复习一下,因为这块用到链表操作了,接下来咱们看一下读操作,读操作 是不是相当于消费啊?
看一下 首先咱们看一下 你所知道的这个链表 是用哪个来标识的?是不是用head来标识的?head是不是指向链表第一个节点啊?
head 就是这个链表,那么你操作的话就操作这个head 就可以了,但是注意一点,你一定要保证这个head 这个指向,你不要把这个head 搞丢了,操作链表最大的问题在哪呢? 这个链表的首指针 指向链表首地址  首节点指针,有时候搞着搞着就搞乱了,一般情况下 我们是不是可以把这个节点 先用一个临时变量先保存起来呀?或者我用一个临时变量指向那个head  然后我操作这个临时变量,那么这样的话我这个head 就丢失不了啊? 这个是切记的一点啊, 好那么我们可以用这个pNode =head; 这样的话是不是把这个head就保存起来呀? 接下来我们操作的话 操作谁呀?操作 pNode  可以吗? 我直接操作pNode

怎么操作呢? 我所谓的操作是不是读出数据来呀?怎么读啊?是不是pNode->date
读出来是不是相当于你消费掉了?消费之后这个节点还能用吗?是不是需要销毁呀?释放,怎么释放?你切记一点 你释放的时候 你千万不要把这个链弄断了,如果现在你就直接把这个pNode给释放的话,这个head是不是就没有了?应该怎么办?要记住 要后移
应该写head=pNode->next 也合理吧? 我们是不是已经保存起来了?
是不是后移了相当于,那么接下来,
这个head是不是相当于跑这边来了?

然后pNode是不是在着? 

 是不是这个,接下来干什么?  free(pNode)吧 ?是不是相当于释放啊? 当然你有一个好处是干什么啊?pNode =NULL; 这是一个好的做法,这个节点,这个内存释放以后 要把这个指针 设置为NULL 那么这样的话 防止 他再非法操作吧?
那这个是不是相当于消费啊?那么我问一下大家,这个里面有没有可能有这样一个情况发生? 我这个链表 一开始 一个节点还没有呢,我消费者 也开始消费了,有没有可能? 你需要干什么?是不是需要判断一下啊?
你判断应该在这啊?如果head =NULL 你说你的直观的想法 怎么做? 你的线程应该是不退出的啊。是不是我们这个 读线程 应该在这个while1 循环里面啊? 你应该是不停的去读吧?
是不是这样的一个逻辑啊? 接着看,那么也就是说什么呢?如果说head =NULL  意味着 是不是一个节点也没有呢,一个节点也没有 那么很显然你在消费的话 直接就(kou)掉了 所以说这个条件如果满足的话,也就是如果head =NULL 的时候,该怎么做呢?是不是需要等待呀? 我问一下大家,我这样写可以吗?我在这儿能不能

我这个head是不是相当于一个共享资源啊?r的版本前面刚讲过这个,两个线程操作共享资源需要干什么?是不是需要加锁啊?加锁加在共享资源出现的位置,这儿是不是需要解锁?

在右图 head 是共享资源需要加锁,完事之后解锁,当然你把这个加上也可以  放一块也行,多两句代码也影响不大,

 现在你加锁成功了,

假如是消费者的线程先执行,假如说 链表删除操作这个消费者 先执行的话,这个条件是不是一开始是不是先加锁成功了?加锁成功 接下来是不是head =NULL,你说你在这该怎么做?当然head =NULL是条件满足了,你该怎么做?说你最直观的想法,是不是解锁, 然后continue;
其实呢 如果说你这个head 如果为空的话,你这个解锁肯定有,那么这样的话,你在这个循环里面,是不是有可能会循环多次?大家注意这个时候就用到 咱们今天要讲的条件变量,那么什么叫条件变量呢?条件变量能达到这么一个效果,当条件不满足的时候 我们的要求是,让你这个线程 阻塞等待而不是说在这来回的循环,你想 加锁 解锁 是不是也比较耗损资源啊?也耗损cpu的 我完全可以让他怎么呀?让他在这个位置他也不解锁了,也别continue了,阻塞等待,

 这个时候 阻塞等待什么呢?他等待什么呢?是不是他应该等待这个head不为空啊?不为空,你在这阻塞等待,如果你不继续循环,他这个是不是就不能判断啊?我们是不是可以这样说啊?如果说这个head 不为空了,那么如果说这个线程能够通知他 能够告诉他一下,是不是他就不阻塞等待 这样是不是更好啊?这个阻塞等待有一个函数 pthread_cond_wait();这个函数有一个效果,就是条件不满足,阻塞等待解锁,那么试问一下 他不解锁 会怎么样?他不解锁就产生死锁了,这个函数内部有一个功能就是条件不满足 解锁,他解锁之后 左图他是不是有可能就获得锁?他获得锁 他是不是就可以生产锁,他生产完以后呢,他就可以通知他(右图) 有东西了,你过来拿吧,那么他就解除阻塞了,那么解除阻塞,他还需要有一个操作 是什么操作?加锁,为什么呀?因为你下面还有一个解锁操作啊? 你不加锁 你下面再解锁 是不是没有意义了?大家注意 加锁 解锁 一定是配对出现的,加锁 有加锁解锁,必须是配对出现的,这个里面这有加锁 这有解锁 这没问题,当然这个内部呢,pthread cond .wait()它里面也有一个加锁和解锁操作,

 这个函数你听一下,咱们后面会讲函数给你说一下,也就是说需要阻塞等待,那么这个pthread cond .wait()函数,我先给你查一下吧?
第一个是全局变量,第二个是锁,

 这个锁,是不是他的互斥锁啊?类型是不是一样的?这里是用的指针吧?再看,那么他在这阻塞呢,他什么情况下才会解除阻塞呢?很显然因为他自己是不是在这不循环吧?他是不是需要外部触发啊?那么很显然,我这个链表插入这个操作里面,也就相当于生产者线程,那么这个线程呢, 需要生产出一个商品来以后呢,需要通知他一下,因为生产出和商品以后 这个head是不是不为NULL了?
你这个head = pNode;是不是有节点了?
他是不是通知他一下?调这个函数,通知消费者线程 解除阻塞
pthread cond signal(&cond); 这个signal和wait怎么连接到一起啊?是不是他们有一个共同的condesion 啊
这个我给你画一条线,这个你马上就明白什么意思了,知道什么意思了吗? 我给大家把这个函数 再说一遍

开始的时候呢,我生产者 消费者呢,我把这个链表插入操作删掉了,直接写生产者线程
这个是消费者线程,之所以从链表开始说呢,因为大家学过链表,那么这样的话呢,咱们说起来比较容易一些,能够理解一些,开始的时候,我这个生产者 是没有这个是不是里面没有这个相当于炉子里面什么都没有啊?你可以把他比作一个打烧饼,烧饼是不是相当于一个共享资源啊?你可以这样理解,那么一开始的时候,一个烧饼没有,这个消费者是不是需要等待这个打烧饼把这个烧饼打出来啊?打出来之后是不是就可以卖给他了,他是不是就可以消费了?一开始没有 他是不是需要阻塞等待?需要等,接下来继续 那么等的过程当中他是不是做的内部操作是干什么呀?是不是需要阻塞等待之后,他需要解锁啊? 条件不满足 阻塞等待并解锁 
那么后来呢,生产者打出烧饼以后 呢,是不是要告诉他呀?你不告诉他在这阻塞等待,他知道吗?他是不是不知道啊?告诉他以后呢?是不是相当于条件满足了?干什么啊?解除阻塞 并加锁

pthread _cond_wait(&cond, &mutext); 这个函数内部的隐含操作就这个样子 如果说你不看资料的话 这个东西你不可能,你自己看的时候 你发现不了,
条件满足 阻塞等待并解锁,
他解锁之后是不是 左图这个线程就获得锁成功啊?

如果不解锁,那就意味着 有可能进入死锁,这个生产者 是不是会一直阻塞在这个锁上啊?如果说生产者 生产出一个商品以后,他要通知消费者去消费了,那么这个函数pthread .condwait(&cond, &mutext); 呢就解除阻塞 并加锁

 我问一下大家 如果不加锁 会怎么样?如果不加锁 这个线程(左图)是不是有可能 又得到锁了?
那么这样的话是不是就乱了? 这一过程肯定是这样的,你生产出来要通知他,他消费完以后呢,他继续生产,
生产者生产出商品以后呢,一定要通知消费者 那么这样的话呢,这个时候消费者 才能够在这个地方呢 解除阻塞,这两个隐含的操作一定要明白,

这个先说到这,这个生产者和消费者模型先讲到这,然后呢怎么去试用这个条件变量 给你说了一下,那么后续的操作 如果让你写 你能写出来吗?我这个代码是不是基本上写出来了?

给大家把这个条件变量再说一下, 条件变量
条件本身不是锁!
作用:条件不满足的时候呢,他能够引起阻塞,那么其实我刚才给大家说的这个位置呢 其实是阻塞pthread _cond_wait(&cond, &mutext); 在什么位置了? 是阻塞在pthread_cond_signal(&cond)条件变量这了, 你看我这个生产者 消费者是不是有一个共同的cond啊?他俩之间连接的纽带就是这个cond

那么这个条件变量,咱们都看到了,那么我们那个函数 wait那个函数 里面是不是加锁 解锁操作啊?所以你应该能想到 他应该必须配合互斥锁来使用,或者你再这样想,那么我这个生产者 消费者的话,是不是我里面都操作到了head这个共享资源变量了?那么两个线程或者叫多个线程操作共享资源,必须要使用互斥锁,再有一个呢,当我们这个消费者线程里面某些条件不满足的时候,所谓的条件是不是head =NULL的时候啊?这个条件是不是一开始就满足了?head =NULL相当于条件满足了,干什么呀?我们这个线程是不是需要阻塞等待呀?等待什么?等待生产者把商品生产出来告诉他  告诉他以后是不是 相当于head就不为空了?那么这个时候呢,我们这个wait函数是不是就会解除阻塞啊?同时加锁,那么这样的话 这个条件变量相当于给多线程提供一个会合的场所
我问一下大家,这个里面,你说我这个 两个线程回合的场所在哪呢?
他俩联系呢纽带是不是cond啊?他俩回合的场所也叫时机 你可以这样理解 只要你这个head不为Null了,是不是我认为 我生产者 我相当于生产出来了,我消费者是不是我有消费了就?是不是可以消费了? 所以说呢,这个条件变量 给这两个线程提供了一个会合的场所,那么我这个生产者和消费者 他俩之间是不是可以通信啊?怎么通信?是不是生产者调用这个函数 pthread_cond_signal(&cond);通知他们呀?
否则的话 你这个消费者下面是不是一直会阻塞等待啊? 是他来通知他的,那么我问一下大家,为什么说通知这个线程 让这个生产者 去做呀?为什么呀?你自己生产的 是不是你自己知道啊?你生产出来你肯定自己知道啊,他生产出来以后呢,这个head就肯定不为NULL了,这还用说吗?是不是就可以通知他了? 是这么一个意思,
再看使用互斥量保护共享数据;
因为我们这个里面这个head他是共享数据,所以 需要使用互斥锁进行同步,

进行互斥操作,
使用条件变量可以使线程阻塞,刚刚前面也说过了,我这个消费者消费的时候呢,只要head=NULL 也就是没有商品,没有商品 消费者 需要等一会, 是不是需要阻塞等待呀?
等待某个条件的发生,当条件满足的时候解除阻塞,那么其实咱们也看到了,那么这个消费者 解除阻塞,是在什么时候解除阻塞的呀?

是不是这个生产者生产出商品以后 调用这么一个函数去通知他呀?pthread_cond_signal(&cond);


条件变量的两个动作:
条件不满足,阻塞线程 里面隐含操作 同时加锁 通知解锁

条件满足,通知阻塞的线程解除阻寒,开始工作
条件满足 当然这个线程 就是这个生产者线程做的事情了吧?生产者线程是不是要通知阻塞的线程 去解除阻塞啊?当然消费者线程内部隐含的操作同时加锁 那么加锁成功之后 他是不是就开始工作了?
总之你要记住一句话,只要多个线程操作了共享资源 要记得使用互斥锁,
咱们看一下 条件变量 相关的函数,
第一个呢,你要想使用条件变量,和咱们钱买你使用互斥量是类似的,首先要定义 这么一个变量,pthread cond t
cond 这么一个东西,先定义变量,那么定义变量之后呢,

第二步:接下来你要干什么呀? 是不是需要初始化呀?初始化的时候 第一个就是 刚刚定义的那个变量吧?第二个参数你也可以设置为NULL就可以了,表示属性

 第三步:不用了要记得释放吧 ,咱们前面讲线程相关的函数,都是这个套路,定义变量,初始化,不用了销毁,是不是接下来,你是不是要把这个条件变量,这个加锁或解锁 这个函数用在哪呢?用在这两个线程的地方啊?所以咱们接下来看一下,那么这个函数int pthiead cond_ wait(这个函数呢,他用在哪呢?
这个函数是不是应该用在 消费者线程啊?
用在消费者线程 他需要阻塞等待嘛,条件不满足的时候需要阻塞等待,第一个参数是cond 是不是就是前面咱们定义的那个变量啊?
第二个 mutex 是不是一把锁啊?这把锁 和咱们这两个线程里面,用的那把锁是不是同一把锁?是不是?是同一把锁,否则的话就没有什么意义了,是不是?
这个函数红色标记的是你务必要掌握的,
条件不满足,引起线程阻塞开解锁 条件满足 解除线程阻塞,并加锁

这个不用说了这是两个变量吧cond:
mutex:

int pthread cond signal 这个函数是在哪调用的呢? 在咱们这个案例当中是不是生产者线程去调用的?生产者线程 生产出一个商品以后,是不是要调用这个函数int pthread cond signal 去通知消费者线程去消费啊?那么也就是说只要这个生产者线程调用这个函数通知消费者线程以后,那么消费者就立刻解除阻塞了,
这个条件变量是不是就是这两个线程联系的一个纽带呀?左图有一个cond 吧?有图也有一个cond吧?他俩是不是一样的?另外呢 

pthread_cond_wait 这个函数是不是还有一把锁啊?这个锁能够mutext让他加锁和解锁 ,大家注意这个加锁和解锁 这个是让他内部去做的

当然这个函数 他可以去唤醒至少一个阻塞在该条件变量上的线程
  他是不是至少一个啊?那么言外之意是不是有可能有多个啊?那么假如说 我这个生产者 消费者呢 不是一个线程
我生产者 5个线程 ,消费者5个线程,那么这样的话你这个消费者线程呢,你调用这个函数以后呢,是不是有可能会唤醒5个子线程啊?有没有可能?当然5个是5个消费者线程?有没有可能?这是有可能的,咱们后面把这个例子讲完之后呢,先给你写个简单的,再来一个多线程的,看试一下,看一下会发生什么问题,

4 使用条件变量的代码片段
这个是代码,代码你看一看 能看明白吗?
producer是生产者  consumer 是消费者
咱们后面还会写呢,先给你说一下,
第一步 首先是不是要 生产者 开始生产了?
生产了以后 是不是要挂在这个 
head节点上啊?是不是挂上去啊?pNode->next =head 

那么生产数据 当然他前提是第一步肯定是干什么呀?前提是他在操作这个head 是不是先加锁啊
pthread mitex lock(&mutex);
加锁以后进行挂上  pNode->next =head 
 head =pNode;
 挂上以后干什么呀? 
 
是不是解锁啊?pthread_mtexulock(&mutex)
解锁之后他是不是调用这个函数 

叫通知消费者线程啊? pthread_cond_signal(&cond)


消费者线程是不是在这儿阻塞着呢?thread_cond_wait(&cond, &mutex);

那么消费者线程也是这样的,他是不是一开始先加锁吧?为什么呀?pthread cond wait(&cond, &mutex)

因为接下来他是不是要操作共享资源吧?这个head是共享资源 如果head 为null的话呢,那么就阻塞等待,等待一个条件的发生吧?
其实就是等待
pthread_cond_signal(&cond) 通知他啊?pthread_cond_wait(&cond, &mutex)

消费完以后是不是要释放了?
free (pNode);
pNode = NULL;

 释放之前是不是要记得这个head节点是不是,要移动一下啊?pNode = head;head = head->next;
 
这个顺序不要搞反了,搞反了 这个链表是不是就断掉了,最后呢要解锁
pthread mutex_unlock(&mutex)

这个sleep 是不是不要加在这个解锁之前啊?
sleep(rando%3);

那么整体上这个代码就是这个样子,代码应该不难吧?

这个呢,咱们就 这几个相关函数,以及这个代码呢,我大致给你缕一缕,你呢,下午来的时候呢,你先自己写一些,你看会遇到什么问题,

 

 

这一块 咱们是用这个使用条件变量呢,实现生产者 和消费者模型吧? 我在这这儿呢 先给大家写了一个基本的框架,你看一下 在main函数里面是不是我们创建了这两个线程啊?
一个是生产者线程,一个是消费者线程,两个线程,这儿呢是等待线程结束,

那么接下来咱们看一下这两个线程的实现函数,回调函数这一块,那么这个生产者呢,我们的名字叫producer
消费者叫consumer

在这呢我们定义一个什么呀?变量吧?这个变量类型是不是就这个结构体呀?

我们这个链表上的每一个节点,是不是都是这个struct node这个类型的,这个大家应该都没问题,
NODE *head = NULL; 定义一个节点,这个是不是head 指针指向一个节点,这个是不是指向链表的头部节点吧,初始化为null

开始的时候是不是肯定这个head为空啊?那么接下来看一下这个生产者怎么写,
这一块咱们还没有涉及到 条件变量这一块呢,这一块是不是只是链路操作啊?
第一步是不是我们要生产一个节点啊?
怎么生产啊? 是不是这个时候我们

pNode =(NODE *)ma1oc(sizeof(NODE));
这个情况你得判断一下 if(pNode =NULL)是不是我们认为这个申请失败了? 你可以试一下  什么情况下会malloc失败呀? 你堆内存耗用的太多是不是就会申请失败呀?
这儿能用这个perror吧?这儿是不是就可以呀?

这个时候呢,进程直接退出就可以了吧?

在进程里面调用这个函数,是不是在这个线程调用这个函数的话,是不是整个进程退出了就?所以说这个函数呢?不到万不得已别乱调,

接下来咱们看,这个节点有了 ,接下来,是不是咱们应该把这个head 给他拼上啊?
我们这个head 是不是应该指向这个节点啊?

 咱们使用的是头插法吧?
这个是不是应该是pNode->next =head;这个相当于头插法,这个头是不是成了这个新的节点的,下一个节点啊?
然后 head=pNode; head 前移 你具体用的时候,你想用这个头插法也好,尾插法也好,这个都不是关键,问题的关键是咱们的这个条件变量在这怎么用?
相当于这个是拼上了? 我们这刚申请出一个新的节点以后,我们是不是应该给这个节点赋一个值啊?怎么赋值 pNode->data =rand()%1000 这个相当于赋值了吧?然后我们相当于先申请一个节点,然后给这个节点赋值了,
那么接下来是不是在这个head的头部 我们插入了一个新的节点啊?同时我们将head 前移,移动到了这个最新的节点上吧?
这个大家可以想一下,那么消费的时候,那么消费的是不是应该是最新的节点啊?
因为你消费的话是不是从头部开始消费啊?所以说你反过来肯定是这个头部节点,这个咱们就

完事了

你为了防止让他申请过快,是不是应该加一个sleep啊? 如果你不加sleep 那么这个while循环会循环太快,有可能会导致堆内存耗尽,而导致进程 是不是最终,会导致这句话执行啊?perror("malloc error");
你有兴趣,你在你的电脑上试一下看看是不是这样的


咱们再看一下消费者,消费者的话呢?怎么写这一个? 那么你访问的话 应该先访问哪个节点啊?是不是头节点啊? 我们可以先打印出头节点的值来,头节点的值是不是head->data啊?这个是comsumer 我们打个C 相当于这个相当于消费了?消费了以后 是不是接下来要进行销毁了?那么这个时候你应该怎么办呢?是不是先把这个head接下来你要保存一下吧?pNode =head;然后head 要后移,head =head->next; 然后是不是要free(pNode)啊?
你这个free(pNode)能不能写在这句话的上面,你要写在上面,这个链就断了,因为你这个一free(pNode) 是不是把这个head 也free掉了?为什么呀?因为他是不是用的同一个值啊?pNode=head;这个细节要关注到啊,
free完以后

 我们正常情况下应该pNode =NULL;防止你对这个节点进行误操作,
这是基本的操作写完了吧?为了防止消费太快 你也可以加sleep

那么这样写的话,大家注意 你这样写 在访问的时候肯定会遇到一些问题吧?

我们把这个值打印出来30, 这个是生产者吧?那么基本的这个框架的代码是不是写完了在这里面我们 只不过是加了一些链表操作而已,咱们的核心操作还没有把?咱们先给他跑一下 试一下,大家想一下 我们这里面哪个是全局变量啊?哪个是共享资源啊? 是不是head啊?我看有些同学 把这个pNode 也写到这个上面了,你把这个pNode 写在上面,不是说不行,如果说你把这个pnode写在上面呢,那么你这一个 你加锁的时候呢,是不是要把这个pNode也加上去啊?也包含在里面去,否则的话呢,这个也成了共享资源了,也出问题,

 

 还有41行,是不是这个错了,后面全错了?只要用到这个的全错了,应该就是这个问题,这个是不是只是,写入不退出啊?

 

立刻死掉,为什么立刻死掉啊? 你说我们这个题目的话,如果说这个,先消费者先跑 是不是立刻就死掉了?因为这个head 没有这个指向啊?如果当然这个大家注意,这个不是每次都立刻死掉,我给你跑两遍,
这个是不是打印出一个节点来呀? 只要生产者先跑,那么他就死不掉,只要消费者先跑,立刻死掉,
那么接下来咱们就加一下锁,因为我们这里面用到了head 这个全局变量,这个head 是一个共享资源,所以呢我们要使用锁,保持线程同步,也就是由原来的并行,现在是不是变成了串行操作?加锁之后你的
应该是变慢了 是不是这样的?


好加锁,我直接在这加吧  那么加锁的话 咱们首先  加锁的步骤 还记得吗?

第一步先干什么? 定义一把锁吧?
pthread mutex_t mutex;

 

 定义一把锁以后呢,接下来,是不是我们在这个主程序里面干什么呀?
要初始化这把锁,初始化互斥锁
pthread_mutex_init(&mutex,NULL);这个完事之后 应该把这个写上  释放互斥锁,
pthread_mutex_destroy(&mutex);

 然后接下来,是不是要找这个加锁的位置啊?肯定是要放在这个两个线程当中吧? 我问一下大家,那么你说我们这个生产者 ,你说应该把这个锁放在哪啊? 是放在25行还是放在 35 这个位置? 当然你的代码一共就这么几行,你放在哪影响也不大,因为函数少嘛, 按照我们正常的说法呢?这个锁应该加在共享资源出现的位置 上下两头, 是不是 那么我们这样的话,这一块是不是不是共享资源啊现在?26-31现在是不是只是生产出一个商品来呀? 然后呢给他附上值
大家注意这一块才是真正的共享资源啊?35 那么我们就放在这,加锁, pthread_mutex_lock 当然大家注意,如果你如果加在这个的话,有的时候咱们看到这个结构显示的时候呢,可能略有差异,但实际上他并不是错误,只是显示的问题,知道什么意思吧?因为这一块26-33 操作并不被这个锁来控制吧?这一点是不被控制的,但是这句话36-38是不是 接下来这两句是不是要控制了?这第一个怎么写?是不是就一个参数啊?pthread_mutex_lock (&mutex); 
解锁是不是在后面呢?
pthread_mutex_unlock (&mutex); 
 
咱们看一下下面这一个

 咱们看一下下面这个呢? 下面加哪是不是加到这个了56?
那么结束的位置在哪呢?多加两行行不行啊?影响不大,这个没有关系的,你就这么一两行 基本上没有什么影响,不要太较真了,
解锁: 因为你这个head移动了,移动了之后,这个的话是不是就相当于 你这个已经在这个head之外了,

 

 当然我们应该对这个head做一个判断呀,如果这个head 为空会怎么样啊?
如果head =null 是不是我们这个程序按理说是不是应该阻塞等待呀?阻塞等待,那么这样的话,咱们就一块把这个条件变量也给你加上,这个head 是不是只要这个消费者先执行,这个head 肯定为NULL吧?当然他这个加锁 消费者线程一开始先执行的时候呢,他加锁成功了,然后判断head =NULL  然后他在这 应该是阻塞等待,若条件不满足 需要阻塞等待,是不是接下来调用这个
啊?那么接下来咱们就把这个使用条件变量的步骤呢给你再说一遍 第一步先干什么? 先定义一个条件变量啊?
pthread_cond_t cond;

第二步是不是要在这个主函数里面进行初始化呀?
看这个步骤啊,条件变量初始化
pthread_cond_init();
你看咱们这个讲线程相关函数,是不是很多初始化都是 什么什么init啊?
这个套路你只要掌握清楚了,很多都是大同小异,
pthread_cond_init(&cond,NULL);

这个不用了干什么呀?释放条件变量,pthread_cond_destory(&cond);
这个步骤的话呢,用起来相对来说比较固定 他和这个互斥锁的用法是不是差不多啊?
步骤差不多,那么接下来,是不是要在这个线程里面去加代码吧?就加两个函数吧?一个是sinal一个是wait 吧?
大家说一下sinal往哪加呀?wait往哪加啊?

wait在哪加 是不是在65加?
pthread_cond_wait(&cond,&mutux)
第一个参数是条件,第二个是锁,这个函数比较重要,我给你加一个注释,这个你务必搞清楚,
若条件不满足,则阻塞等待,并解锁;
若条件满足,
那么大家注意什么时候条件满足呀?
你这个head!=null 是不是已经判断不出来了,因为你在这里面呢,那么所谓的条件满足其实是被谁通知的呀?被生产者去通知的 被生产者线程调用pthread_cond_sinal函数唤醒的吧?通知也行,
他的这个条件满足其实是被生产者,调用了这个函数 signal 函数通知,然后这个 也就是条件满足,解除阻塞并加锁,

 这个函数你务必要搞清楚,如果说你仅仅是通过看书 有的东西 这书上不给你说 这个 或者说的不透彻,它里面隐含的东西你并不知道,他怎么知道解哪个锁呀?是不是你传给他了?&mutex; 这个条件变量 是不是和signal的参数一样啊?

 那么接下来咱们看一下,看看这个生产者,那么生产者 这个代码在哪加呀是解锁里面 是这个里面还是外面啊?为什么呀?这个你说对了,这个本质上是这个意思
那举个例子,那假如说你这个解锁 你还没解锁呢,你通知他 他能够加锁成功吗?你一通知他 是不是就 那个全局变量 阻塞的那个全局变量 那个线程是不是解除阻塞了?解除阻塞之后他是不是要试图加锁啊? 他能加锁成功吗? 如果你写在这个unlock值上的话能吗?不能因为你这儿还没有释放锁呢,他当然加锁不成功,所以说 你肯定是什么呀?解锁之后再通知,这个他是不是就立刻获得锁了?
你要知道这个原因 ,这个小的细节你要知道,
当然不严格的说,你放在上面,影响也不大吧?因为这个瞬间就完成了,
唤醒 或叫通知 通知消费者线程解除阻塞
pthread_cond_signal(&cond);

 那么整个 我们这个代码是不是写完了?大家注意看,再看这个cond 是什么关系?69和48是对应的,这个函数一调用,他是不是理解解除阻塞啊?大家注意 第一个 他解除阻塞,是谁的阻塞啊?是不是在这个条型变量上的阻塞啊?第二步 如果说他在被阻塞 应该是被这个锁阻塞了,有没有可能?
这个函数48的话,是不是可以唤醒,至少一个线程吧?当然你这里只有一个的话,这个东西不存在, 假如说 我这个线程,我调用singal 之后呢,有好几个消费线程,好几个消费线程  假如说一次性全部 在这个条件变量唤醒了 ,那么 这三个消费者是不是也会抢这把锁啊?是不是只有一个线程能获得锁啊?怎么办?那么这个线程再次阻塞,是不是就阻塞这个锁上了呀?而不是这个条件变量,要搞清楚这个 这是里面的一个小细节,我给你说一下,这个清楚了吗? 这个条件变量69和这个条件变量48是不是有关系呀?
这个函数一执行69,这个函数就会立刻从条件变量上解除阻塞,并试图加锁,是不是要加锁啊?接下来,如果说加锁成功了,如果加锁成功了,那么就执行后续代码,如果加锁不成功,那么肯定是 是不是有好几个线程同时被唤醒吧?同时被唤醒,但是只有一个线程是不是可以获得锁啊?大家注意这个锁呢,同时只可能有一个线程获得,不可能有两个以上的线程同时获得,能清楚吗?这一点要注意,

 这个代码我们写完了,咱们测试一下是不是这样的,那么你看一下(生产的一定是消费的呀?)消费的一定是最后一个生产的呀?

 看看有没有连续两个生产的呀 比较少见,除非是他那个ruan的呢, 正好呢是rund0看有一个  到这, 这个526 980 是不是最后一个消费 是980啊?然后再消费是526吧?是不是正好和这个相反啊?没问题吧这个代码,这个代码咱们就先说这么多,

 这个很好加,在这里加不就完事了吗?
这里for (int i=0;)  这个能不能这么写其实不是说行不行,是看版本,是不是同学们? 
那么咱们在上面定义吧?

 

 我一块给他创建了可以吧?这个是不是改一下这个就可以了&therad1[i] &therad2[i]
当然我这儿是不是要改一下啊?

 

 

 咱们把这个子线程 是第几个也给他打印出来,是不是应该定义数组啊? int arr[5];
这儿的话,我是不是就可以直接拿值了?&arr[i] 
那么这个样的话这个i是不是传进来了?那么咱们在这个线程里面要把这个值来拿一下,我们做的目的是什么呀?我们看一下这个值是第几个子线程打印出来的,咱们就可以看一下,
这儿的话 是int n =*(int *)arg;
这句话 我直接给他复制 拷到这个里面来60,然后我这个打印的话 在哪打呢?这儿你看的时候看的容易一些,

 

 这个初始化,是第几个子线程打印出来的,其实改起来没有几句话,咱们就把这个代码试一下 那么是不是会如大家所说的那样 (kou掉了)
 死掉了, 再来一次 这又跑多了一些,又死掉了, 现在比较稳定了,你说你把这个程序卖出去行吗?是不是第二天 赶紧有问题有bug啊?每天找你 这个是不是叫bug啊?他不是说每次都发生,但是一发生马上死,这就很危险,咱们先看一下 咱们先分析一下这个值,首先咱们随便找一下吧?

 C[3]:[567] C[4]:[771] 这两个是不是消费者啊? 上面有个567 上面有个675  675 这也没问题,然后剩下是不是771啊?也没问题, 然后看一下这个586 最后一个消费的是586吧?当然这个 最终你分析起来肯定是对称的吧?知道什么意思吧?你生产的,因为你这个头节点肯定是第一个消费的吧?那么接下来咱们分析一下 这个为什么会(kao掉)这个需要咱们去分析一下,原因在哪,咱们也应该能够大家 差不多也能定位出来,就是说肯定在消费者这,的确是在消费者这,那么大家想一下 能够定位到哪吗?71这一块吧?wait函数,

他没访问什么什么东西,这个(kaode)一般都是神?非法访问内存吧?那么想一下 哪有可能会出现非法访问呢?你说head 如果说为NULL了 你这样指向是不是(kaode)掉了?head<-data 那么这儿有可能会使head 为NULL吗? 大家想一下,的确是,首先我告诉你是这样的,这个head肯定是为空造成的,那么现在让你想一下的是,为什么在这head 有可能为空,
我问一下大家 这个函数49 pthread_cond_signa1(&cond);是不是我给大家说过了?是不是有可能会使至少一个线程解除阻塞啊? 假使说这个时候 咱们分析一下特殊的场景,假使说这个生产者 只生产了一个商品,咱们来一种极端的情况,只生产了一个商品,这个时候呢,他是不是要调用这个函数 唤醒所有的子线程啊?这个时候正好,至少有两个子线程 解除阻塞了,也可能有多个吧?咱们就说至少有两个,你也可以认为有多个,也就是说至少有两个线程在71这个条件变量上解除阻塞了,那么解除阻塞 这两个线程只有一个线程能够加锁成功啊?他解锁成功那就意味着他73-83被消费了,他消费没事,第一个没事吧?让他呢 把这个head = head->next; head 是不是置空了?如果只有一个节点 你看一下 这个head是不是肯定为空啊?只有一个节点的话? 接下来,这个第一个线程,消费者线程 他执行结束了,然后另外一个线程是不是他也在这71 是不是这里面有一个加锁操作啊? 他也加锁成功了,加锁成功了是不是继续往下走啊?继续往下走 到这74就死掉了,

 

 也就是这个head 的值 是不是被一个消费者线程被改掉了?改成空了吧?是不是这个怎么改?如果说你把这个bug改一下, 你怎么改?是不是你在这需要加一个逻辑啊?你需要判断一下这个head是不是为空啊?为空 那你还往下走吗?是不是不走?
光写continue行不行?你是不是得把锁释放了啊? 为什么? 你这里面是不是有一个加锁71的操作啊?你不释放锁 就死锁了,所以这句话必须有,这样的话是不是合理呀? 因为你这个pthread_cond_wait函数的内部本身是不是有加锁和解锁的操作啊?你这儿肯定是什么呀?他加锁成功了,然后往下走,往下走的时候呢,这个head 为空了,所以照成(kou掉)的情况,在这呢,你为了避免他(kou掉)所以我先做一个判断,如果为NULL我们就解锁76,为什么解锁啊?因为前面你已经是解除阻塞加锁了,所以在这,当然这个是unlock 所以说  这儿你要知道为什么,

 这个原因咱们一会儿再给你写一遍,首先我再给你叙述一遍,作为一种极端情况,假使说这5个生产者线程里面,你就考虑这种极端情况就可以了,那种刚一开始就(kao掉)肯定是这种情况,因为你这5个生产者呢,你不见的这5个生产者 全部 是不是每次都能生产出商品来呀?有可能只执行了一个,那么假使说 就生产出一个商品,那么这个时候呢?那么生产者 是不是就调用这个函数 给他唤醒啊?49 是不是把所以的消费者线程唤醒了?

 唤醒之后呢,只有一个线程能够加锁成功吧?是不是肯定这样的?因为锁的话他不可能有两个线程同时获得,否则的话这个锁就没用了,那么这个消费者线程,这个得到锁的消费者线程呢,他得到锁以后继续往下执行, 然后呢他是不是消费了?消费了以后呢 他是不是把这个head 83置为空了? 他置为空了 他完事了,完事呢?另外一个消费者线程是不是在这儿71阻塞 另外的消费线程阻塞在哪了?阻塞在什么位置了?是不是还在这里面?71 但是他阻塞不是阻塞在条件变量上了,阻塞在什么位置上了?阻塞在加锁上了,他阻塞在加锁这个位置了,然后呢 刚才那一个消费者线程是不是他已经退出了?他释放锁了吧?他释放锁是不是这四个消费者线程是不是有可能又获得锁了?又获得锁 往下执行,81这儿立刻(kao掉)了 ,就是这种情况,
好 我们加上这个逻辑以后呢 74 一看是他的head 的话呢,咱们给他先解锁77 然后给他continue就可以了吧?
如果不解锁 是不是造成死锁了就?
咱们看是不是这样的? 貌似不死了吧?

 咱们现在分析一下这个值对吗?

看这两个P[1]:[311] P[0]:[605] 这两个是不是第一个和第零个线程生产的啊?是不是连续有四个啊?P[1]:[311] P[0]:[605] P[2]:[661] P[2]:[878] 是不是连续有4个啊?那么消费的话是878 661 605 311 是不是正好对称啊?

再看一个P[1]:[522] C[3]:[522] 这个也对称吧?

消费者P[3]:[622] 622一定是什么?一定是有的?

再看一个P[3]:[466] C[0]:[466]  没问题吧?

这个是P[2]:[90] C[2]:[90]

P[2]:[542] C[0]:[542]都没问题


看这四个 P[2]:[445] P[2]:[179] P[2]:[887] P[1]:[348] 
C[3]:[348] C[4]:[887] C[4]:[179] C[3]:[445] 这个是不是也是对称的啊?一定是对称的,这个相当于什么呀? 这个是不是相当于,我们在这个头部插入
消费的话 是不是也在头部消费啊?肯定是对称的一个东西 这个我们就说到这,

下面咱们就做一个总结,咱们看一下 条件变量的使用
条件的使用步骤 第一步先干什么?
1.定义条件变量吧?
pthread cont t cond;
2.初始化条件变量
pthread cond init (&cond,NULL);
当然用完了得释放吧?当然我们这个的话 是在哪呢?
3.在生产者线程中调用
pthread cond signal(&cond); 这个是不是只有一个参数吧?
4.在消费者线程里面呢?
pthread cond wait (&cond, &mutex);
5.最后呢不用了在哪释放啊?在主线程里面释放啊?
pthread cond destroy(&cond); 只有一个参数
 这个 ,我把这个刚才那个为什么发生core掉的情况给你总结一下,你自己想一想

这个是多个生产者 多个消费者 在执行的时候呢,core掉 原因分析
那么你考虑像这样这一种情况 你可以就考虑那种特殊情况就可以了 因为他只要引起core掉是不是就可以了?
你或者是我这个5个生产者呢,我只有两个生产者生产了两个产品,然后我5个消费者我都跑起来了,是不是也会产生core掉啊?只要是生产者 生产的数量小于消费者执行的线程数的时候呢,都有可能会引起core掉,是不是这样的?好咱们考虑这种极端情况,那么如果说 假如 只有一个 生产者 生产了一个节点,那么他这个时候是不是会通知消费者啊?此时会调用pthread_cond_singnal 通知消费者线程,那么此时呢,唤醒的消费者线程可能有一个也可能有多个

如果说是一个的话不会被core掉,如果是多个呢是不是就core掉了?
消费者线程,此时如果若有多个消费者被唤醒了, 被唤醒的意思是不是就在这个条件变量上解除阻塞了?被唤醒了,则最终只有一个消费者获得锁吧?然后进行消费,此时会将head置为NULL,因为你只有一个节点吧?消费完了是不是没有了?然后其余的几个消费者线程 只会有一个获得锁啊?
因为他们是不是此时是阻塞在了那个锁上啊?因为他们已经从条件变量那解除阻塞了吧?要分清楚 那么这个引起这个线程阻塞的位置一共有两个地方吧?一个是条件变量上吧?另外一个是那个锁上,这个搞清楚,

获得锁之后,然后读取head的内容就会core掉,这个你自己要琢磨琢磨

然后再给你总结一点
就是说在使用条件变量的线程中,能够引起线程阻塞的地方有两个
1.在条件变量处引起阻塞
这个是不是会引起阻塞啊?那么这个阻塞会被谁唤醒啊?会被谁解除阻塞啊?是不是会被这个生产者 调用pthread_cond_singnal唤醒啊?解除阻塞

2.第二个阻塞在什么位置啊?锁,互斥锁也会使线程引起阻塞,因为这个锁是不是互斥的啊?如果说有一个线程获得锁了,其他线程获得这把锁吗?获得不了,只能阻塞,当然这个锁解除阻塞,其他线程是不是只要释放了锁 他就可以解除阻塞了,其他线程解锁会使该线程解除阻塞

 你得知道 这个阻塞其实是在两个位置 并不是一个位置是不是这样的?

那么接下来咱们讲一下信号量这个,咱们把讲义打开,

 

 

 

 

 

信号量相当于 你可以把他理解为多把锁,可以理解为加强版的互斥锁,

关于这个 这些函数我先不说呢,我给你 咱们看一个图,这个信号量他也能够完成一个生产者和消费者模型,

你这两边的代码是不是都有啊?你先看一看我给大家说这个图
那么这个图怎么理解比较好理解呢?
大家想一下这个车库,车库里面是不是有很多车位啊?有很多车位,那么我问一下大家,如果让你使用互斥锁来控制这个车位的话,达到一个什么效果啊?什么效果?是不是如果让你使用互斥锁的话 你想一下,来控制这个车位的话,这个车库里面的车位是不是有多个啊?按理说 这个车位 如果说有多个的话,是不是可以允许有多个车进去?如果你使用互斥锁的话,会达到这个效果吗?是不是只能 进一个出一个啊?进一个出一个 进一个出一个,这样的话是不是很明显 是不是不合理呀?能知道什么意思吗? 这个时候我们可以使用信号量,那么信号量呢,他这个值是大于1的 也可以=1的,我们可以让他大于1,那么言外之意是什么呢?这个车库里面有多少个车位,我就可以让多少个车同时进去,那么就看这个车位有多少个了?比如说有10个车位,当然我这里面比如说  红色表示已被占用了,蓝色表示空闲车位,现在来说我这里面一共有几个车位啊?有6个车位

有6个已经被占用了6个是空闲的吧?这个时候按理说我们这个口是不是可以有6个车同时进去?如果让你使用互斥锁是不是只能有一个车进去啊?那么有一个车出来之后,释放锁之后是不是另外一个车才可以进去啊?很显然这样不合理,这个时候用这个信号量他可能比较好用了,
咱们看一下是什么意思?这个图 在这个图当中,那么首先这个车位,这个车库里面的每一个车位表示什么意思?那么这个是不是这个车位 是不是可以把他比喻成共享资源啊?你这个车是不是相当于线程啊?车把他比做成线程,车库呢 表示成共享资源 然后这个管理员相当于什么呀? 那么你进不进去 管理是不是说了算啊?管理员让你进去你就进去,不让你进去是不是进不去啊?所以说这个关联相当于锁,这个比喻给你说完了,接下来看一下 这个时候呢,我们这个车库里面有6个车位 所以呢我初始化的时候呢,应该是6啊 那么也就是说6个车可以同时进去 是不是很显然,那么这样的话是不是 我们就可以说这6个线程是不是可以并行执行啊?能理解吗?如果说你使用这个互斥锁的话,是不是意味着 只能有一个车进去 一个车出来,那么很显然这个肯定是串行的吧,那有同学就说了,那么你现在有6个车位,如果来10个车怎么办?那也是不是也只能有6个车进去啊?那么是不是仍然可以做到有一定程度的并存性啊?

是不是 虽然说不能说让10个车全进去,但是也做到了在一定的程度上,有多个车同时进去,那么也就是说有多个线程 同时在执行,当然我们在用的时候呢,我们这一个例子模拟的时候呢,还是用的是头插法,这个和咱们刚刚讲过的一模一样,在这呢 我就不再啰嗦了,那么你看一下这个代码,
这个意思明白了没有?也就是说你这里面有几个车位,也就是说可以让几个线程同时进去,那么不像我们的互斥锁只有一个车进去,一个车出来,

关于这个呢,我先给大家,把这几个函数给你捋一遍
那么这个呢要使用信号量了,

那么这个信号量的值呢,初始值是可以大于1的,如果等于1 就类似于互斥量,你或者说 这个互斥量就相当于这个信号量的一个特例,=1的时候,
那么看一下怎么用,首先还是那句话 先定义
变量sem_t sem;
先定义一个类型的变量,那么接下来,进行初始化,那么我问一下大家这个初始化int_sem _init(应该放在什么位置?是不是main函数里面啊?
那么这个定义变量应该放在哪?sem_t sem是不是全局的?这个和那个用法是基本上类似的,
初始化的时候第一个参数,sem表示信号量变量,这个参数是不是刚刚我们定义的那个啊?

这个pshared:这个值有两种,0 表示线程同步,1 表示进程同步
在这的话 我们应该写0,

第三个参数最多有几个线程可以操作共享数据
value:最多有几个线程操作共享效据

在我们的这个图里面,你说你应该赋几啊?蓝色表示空闲的,可用位置是不是6啊? 在这你赋6就可以了,
这个函数 成功返回0,失败返回-1,并设置errno值
在这儿是不是就可以使用errno了?
因为你初始化是不是只有在这个主线程里面去使用的啊?在main函数里面吧?
接下来int sem wait这个wait函数是不是有点类似于pthread_cond_wait()啊?这个函数 他是一个阻塞函数

 那么什么情况下他就阻塞呢?如果说 这个车库里面的 如果说车库里面这个可用资源数大于0 他就不阻塞,如果等于0 就阻塞,那么言外之意就是什么意思啊?如果说我这个里面有车位的话,我这个车是不是就可以开进去啊?如果说 这个车位没有了,没有一个是空闲的,那么这个车是不是只能在外面等了?类似的,那么当然如果说 假如说我们这个图里面 现在有6个车位,这个时候你调用sem wait阻塞吗?是不是不阻塞啊 进去之后 这个空闲车位是不是减少了一个啊?那也就是说呢?调用这个sem wait之后,那么这个初始值,也就是说这个sem 这个值 在这里是不是已经初始化了value?
int sem init(sem t 
sem, int pshared, unsigned int value)
这个值会--, 当减小到0的时候呢,你再调用这个函数 sem_wait干什么呀?阻塞,那么有减的是不是就有加的啊?那么言外之意,这个车库里面,如果说有一个车开出去了,那么这个车位是不是会增加?
那么这个时候就相当于调用了这个sem_post这个函数,调用这个函数以后呢,车位增加 那么相当于什么呀? 这个sem++;
你肯定是什么呀?有加有减是不是才能达到一个动态呀?
如果说你光进去不出来 那么这个车库的话是不是以后再也有车进不去了,是不是?这个也是成功返回零,失败返回-1;
再看sem_trywait我这个不说你应该能想得到吧?是不是咱们前面讲过好几个了?

sem_trywait是不是尝试加锁的意思啊?是不是有可能这个是不是加锁不成功啊?当然如果说这个sem这个初始值,如果是0的话,如果说这个值为0了,那么你说这个trywait是不是就获得不了这个锁啊?当然这个不管这个sem大于0还是等于0,他是不是都会直接返回啊? 因为他是一个不阻塞的函数,
还有一个最后不用了int sem destroy 销毁,这个套路和咱们前面讲的

互斥锁完全一样啊?先定义,然后初始化,接下来是不是要用啊?
这个是不是有点类似于加锁啊int sem wait
  
这个是不是有点类似于解锁啊?int sem wait(


最后不用了int sem destroy(释放,都是一样的,

int sem wait 这个他如果说条件不满足的话 他会怎么样?如果sem =0的话是不是会阻塞啊? 我说这个类似的 但不完全一样,
这个道理差不多的 ,这个是不是相当于加锁啊? 我举个例子 如果说这个sem这个值为0了,你说这个函数是不是会阻塞啊?如果大于0是不是会立刻解除阻塞啊?

我举个例子 假如说我们在初始化的时候,我们这个value=5,那就意味着 是不是有5个线程可以同时执行啊?那么当这5个线程执行完以后 再调用这个函数的话 int sem wait 是不是会阻塞?
当然如果说你这个线程里面 你调用了sem_post这个函数 那么就意味着这个sem函数加一啊?那么只要大于0 那么这个函数sem_wait 就不再阻塞  能清楚吗?

这个代码你可以看一下,这个代码 和咱们刚才说的那个生产者消费者 完全一样,只不过原来 用的是互斥锁加条件变量,在这里呢,我们用的是信号量,
这个是不是生产出一个商品来呀?然后调用sem_wait 调用sem_wait 以后你说这个值,sem_producer 这个值会干什么呀?是不是会--,
那么他生产出一个商品以后,sem_post 这个sem_consumer值是不是会++?
那么一开始呢 这个消费者呢右边 sem_wait 消费者这边一开始是不是应该是阻塞啊?为什么是阻塞啊?一开始是不是应该是没有商品啊?理应是没有商品的话,没有商品的话他需要阻塞等待, 他阻塞这个sem_consumer 这个锁这儿了, 我问一下大家,这个值初始值应该是几啊?是不是0啊?

那么这儿呢?左图sem_producer 初始值应该是几?是不是应该大于0的数啊?等于1是不是也行啊?应该是大于0的数,等于1是不是就类似于互斥锁那个了?给那个是类似的了,那么这个值是>0

那么看有图 sem_post( &sem_producer)这个值 调用这个sem_post以后 这个值是不是会++啊?他一++ 是不是会唤醒阻塞这个左图位置的一个sem_wait 一个线程啊?为什么?因为一开始假如说这个sem_producer=0的话,那这么一++他是不是大于0了?大于0之后 他是不是解除阻塞了?所以说通过这个图我们可以看得出来

这个图上呢,我们这个图呢,我们使用了两个信号量啊?他们俩是不是来回的相互通知?这一点有点类似于 比如说举个例子 小卖铺,小卖铺的话是不是需要进货啊?如果说小卖铺货是满的,你说他用进货吗?当小卖铺的货少了,他是不是要打电话通知送货的人啊?送货人 我这需要货了,给我送过来,配送过去了吧?当如果说他又没了东西 他是不是又打电话啊?当然 人家那个送货的人呢,我这儿有新的商品,是不是你也要货啊?我给你送过去,他俩之间是不是来回通知啊?那么你这个右图的sem_producer是被他左图sem_post唤醒的吧?
那么这个右图的sem_post->sem_wait是不是被左图的这儿 右图的sem_post ->sem_wait唤醒的吧
他俩来回通知 所以你使用信号量的时候呢,是不是你至少得有2个啊?一个能行吗?一个不行

这个我先说到这

咱们改一下 

 

 

加这个信号量的位置是不是就是和这个锁位置类似啊?
咱们先看这个怎么加 
第一步定义信号量,信号量是不是应该定义两个啊?
sem_t sem_producer;
sem_t sem_consumer;
接下来是不是在主程序里面进行初始化啊?

初始化信号量

sem_init(&sem_producer,0, 5);0表示线程 1表示进程吧?
第三个是不是初始一个值 啊? 那么你想一下 这个sem_producer是不是控制这个生产者的啊?他的话是不是一开始能够生产的啊?这儿呢 我们定义为5,

sem_init(&sem_consumer,0, 0);消费者,这个一开始能消费吗?一开始没有商品吧?他是不是一开始是0啊?

因为一开始的话,这个消费者应该是需要阻塞等待的,是不是这个生产者生产商品以后他就可以通知他呀?

最后不用了记得释放

那么接下来是不是开始找位置了?是不是很好找啊?原来你加锁在哪加 就在这用sem_wait这个参数应该写谁啊?
sem_wait我是不是给大家说过?这个sem_wait如果说这个信号量的值如果是0的话是不是就阻塞等待呀?如果说大于0 是不是就往下执行啊?就不阻塞  那么你说一开始的话能生产吗?所以这个值是不是应该>0的啊?
大于0 我们应该填哪个?是不是sem_wait(&sem_producer) 那么这样的话这些就不要了吧?

这一块呢?那么他生产出一个商品以后,是不是他应该通知消费者啊?他怎么通知?是不是sem_post(&sem_consumer)是不是消费者啊?因为这个值一开始是为0 啊?那么一开始是0,这个消费者应该是阻塞的这个上面了吧?那么做这一个操作之后,他是不是相当于做了一次++操作啊?

sem_producer这个值一开始初始值是5是不是?那么是5就意味着 是不是可以连续生产5个商品啊?直到这个sem_producer值为0之后,他是不是就会阻塞了?这个相当于--操作,

这边是不是 要换成信号量操作吧?
sem_wait(&sem_consumer) 开始这个值是几啊?一开始,是不是0啊?所以说他一开始是阻塞等待,阻塞等待生产者把商品生产出来是不是告诉43他呀?怎么告诉他呀?是不是这一个57就可以唤醒他呀?你看他俩是不是同一个值呀?这个相当于什么呀?这个是不是相当于--操作呀?

我问一下大家,在这我还用写head=null吗?完全不要了。因为你这个sem_wait本身可以引起阻塞啊?
这样的话是不是要换掉啊?64 换成什么呀?
sem_post(&sem_producer);那他消费了一个那就意味着什么呀?那么生产者是不是又可以多生产出一个来呀? 那么举个例子 比如说你 生产者 你一共可以生产5个,然后呢 我消费了一个,那就意味着你又可以生产一个啊?道理是一样的
就写完了,就这么两下

你只要会写那个了,是不是这个就可以照葫芦画瓢,这个执行了64以后那么这个值sem_producer是不是就会加1啊? 他+1了,如果说这儿37阻塞了,他是不是就会被唤醒了?所以说 咱们看一下

怎么切换到另一个窗口去?ctrl+两次w 

左图这个是消费者 , 右图是消费者

左图37是不是一个啊?43
右图 57 64 
他们之间是什么关系呀?
sem_wait这个函数一开始是不是可以执行啊? 在生产者这,他生产出一个商品之后,他是不是往下执行啊?执行43sem_post(&sem_consumer) 你说会发给谁呀?是不是跑57这儿来了?
这个是不是43通知57了?
接着看,那么他是不是就唤醒了?那么唤醒之后呢,他是不是往下执行啊?他是不是相当于消费了?消费完一个以后那就意味着

这个生产者 是不是可以多生产一个啊?这个是不是相当于通知他的?那么你看这个变量,我给你换个颜色,红色是不是一回事?他们之间的联系能看清吗?是不是他俩来回的相互通知啊?那么你一开始的话 你生产者的话,你能不能生产呢?是可以生产的,为什么呀?因为你初始化的时候你是一个大于0的值,比如说我刚才是不是5啊?那就意味着我这个生产者是不是可以连续生产5个啊?当想生产第六个的时候,还能生产吗?不能生产了,那就意味着他是不是要阻塞啊?那么这个时候呢,他如果是消费者呢?如果是执行起来了,那么执行起来以后呢,我问一下大家,他执行起来以后,那就意味着什么呀?你这个post 也就说什么呀?你这个44sem_consumer是不是通知他以后 他就会解除阻塞啊?解除阻塞往下执行,这个sem_post以后 这个值是不是就++了?
++之后,那么原来生产完了,是不是现在继续又可以生产了?同理反过来是不是也是一样的?
这里面我给大家出一个思考题 什么思考题呢?
我这个生产者43生产完商品以后,我执行了sem_post这个函数了,我是不是通知他了?你说通知他 他就立刻执行吗?会不会?是不是还得看cpu时间片啊?那么也就是说  我们这个生产者 线程里面 那么是极有可能 在一个cpu时间片内,把这5个商品生产完了,有没有可能? 在这我如果不加sleep45的话 他是不是有可能?
他一在一个cpu时间片 他有可能执行了5次,那么导致最后这个值37sem_producer 值为0啊?为0之后他是不是就阻塞了?他阻塞以后呢 有可能就让出cpu了,让出cpu呢 没有关系,其他线程呢?没有关系 因为他43已经通知他了57他就有可能在这,是不是就可以解除阻塞了?前提是 他得到cpu时间片以后,他是不是可以解除阻塞,解除阻塞 他是不是就可以消费啊64?
他消费也是一样,他是不是有可能连续消费5个啊?
为什么呀?因为你这个sem_consumer 44 值是不是变成5了?57他这里是5,所以说呢,他可以连续消费5个,是不是同理 这边也是一样的37?

 

 当然我们这个的话 肯定是一个一个的来呀?因为我只有一个生产者 一个消费者,而且我在这里面是不是加了sleep 啊?当然有的时候他是不是会连续生产多个啊?

 

 看有多个的情况吗?有两个以上的吗?也有可能 当他sleep0的时候是不是有可能?
这个代码呢,咱们就写到这,那么至于说这个 其他的话呢,就没有什么了,大家试想一下,如果说这一个,多个生产者 多个消费者的情况,会不会出现cord的问题呀? 是会的,之所以这个用处不是非常的广泛,就出现在这个位置了,大家想一下 我们这个 链表的话 这个head是不是一个共享资源啊?他也有可能出现 我们上节课 是不是刚刚给你说过 一个corde的情况啊? 给那个是类似的,你呢,有兴趣你可以把这个改一下 改成各5个线程,你看他会不会corde掉,一定会corde掉,这个先说到这,
咱们总结两句
信号量的使用:
1.定义信号量变量
sem_t sem;
2.初始化信号量
sem_init(&sem,0,5);初始值比如说是5
3.接下来你是不是要在这个线程里面去使用它了?
大家注意 这个sem_t sem 是不是一个全局变量啊?不要搞混了,这个是全局变量,因为你一个变量是不是在这个线程里面会使用啊?你定义在这个主线程里面,那么别的线程是不是无法访问这个变量啊?
init 接下来呢
3.是不是要相当于加锁和解锁了?怎么用?
加锁sem_wait(&sem1) 只有一个参数
解锁sem_post(&sem2)
你使用信号量是不是至少得两个啊?是不是得来回通知啊?

 那么对方那边 是不是正好返过来呀?
中间是共享资源  或者叫共享变量也行
最后这个不用了记得释放
4 释放资源
sem_desotry(sem1);
sem_desotry(sem2);
这个就说到这,信号量这张呢,你这个用在一个生产者,一个消费者里面没有问题,你用多个里面会有问题,你可以试一下,原因 给我们分析的那个是一样的,你可以尝试一下

咱们第一天 讲的是 linu系统的常用命令
1. linux 常用命令
ls cp  mv  rm  rmdir mkdir touch  chmod chgrp chown which whoami
tar 重点讲tar  zip讲不讲无所谓 rar  find  grep
这些你用的多之后呢 你自然而然熟悉了
当然我们在讲这些命令之前,有些基本的概念再讲一下,比如说linux目录结构 以及常见目录的作用
这个你自己能说出来就行了,也不需要死记硬背,这些东西即便是你呢 比如说有些同学不知道,也不影响你开发,但是作为你说你使用过linux 人家问你一些 有哪些目录啊 你得知道,因为什么呀 你不能跟他们用过一次mas就不用了,你跟他们不一样,以后呢我们开发的时候呢,我们绝大部分时间都在linux 上进行开发,特别是做后端开发的,做服务端开发的

2.vim的使用:
三种模式 命令模式  编辑模式 末行模式
其他的就不说了 一些小的知识点 看一下就行了,
假如这个之后 我给大家讲什么呀?gcc的工作方式
工作流程 gcc一共分为几步啊?是不是四步?
第一步什么呀?预处理 至于什么参数咱们就不说了
编译  汇编  链接
以及每一步怎么做的,你自己可以熟悉一下,但是我讲的时候 以及面试的时候 一般只要能够说出4步就可以了,至于说每个参数 怎么用的?这个平时不怎么用 谁记得住啊?
如果你能记得住更好 这个是gcc的工作流程 ,那么讲完这个之后 
还讲了什么呀?
 

库的制作和使用:
关于库的制作和使用 那么 静态库的制作和使用到是很简单,动态库的制作和使用略微不同啊?这个稍微的麻烦一些,这个具体怎么做 我也不说了
有一点 还有一点 动态库加载的时候 报错问题的处理:
我们的解决办法是不是给你讲了好几种啊?但是你起码得把我给你讲的那种最常见的方法给记住,export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:./后面加路径,你知道这个是什么意思吗?
把这些东西写在哪呀?是不是写在用户级别的配置文件里面去呀?这个你得知道
是不是配置文件啊?配置文件是哪个呀?是不是.barchrc啊?这个.barchrc是不是在用户的家目录下,


这两个库 静态库和动态库有什么区别?
其实只是围绕着一个核心来说的吧?那么静态库是不是已经把这个库打包到可执行程序里面去了,但是动态库不是这样的,动态库是运行到那的时候呢,才会去加载,如果说加载的时候找不到这个路径,他会报错,报错怎么解决 是不是这个问题呀?export LD_LIBRARY_PATH这么解决就可以了

3.makefile和gdb调试
关于makefile我给大家要求这样的,你至少来说能够看明白一个makefile 然后如果让你改的话 能够改 怎么改就可以了,真正你参加工作的时候呢,真正让你写一个makefile的时候呢,不太多,如果让你写你也不用害怕,直接把别人的看懂了是不是一改就可以了?

关于makefile你务必要掌握的一点是,把他这个规则搞清楚 什么规则?
目标:依赖
命令 这个命令是不是就是将依赖生成目标的命令啊? 大家注意 这个命令前面有一个什么东西呀?(tab)务必掌握
在这个makefile 里面是不是我们还 还用到 什么什么函数了,变量  伪目标等等

咱们用函数 哪个用的比较多吗?
变量的话我们介绍三个变量,自动变量,自带变量,和用户自定义变量,一共三类,其中有一个注意点,关于自动变量,只能用在哪个位置啊?只能用在命令当中,不是命令行,是命令当中,
那么伪目标 什么叫伪目标啊?也就是说不检测依赖 不检查更新,怎么定义伪目标啊?是不是
(.houni)啊?后面写一个目标名就可以了?还有一个就是什么叫终极目标啊?
你这个makefile当中是不是第一个出现的目标叫终极目标?那个也是最终目标,

关于咱们gdb调试就不再多说了,特别注意的是 你要使用gdb调试你必须加一个参数 在编译的时候 要加参数,编译的时候加-g,否则的话 不能用gdb调试的 你看不到任何的代码,这个里面 这个gdb调试的话呢,咱就不再过多说了,里面有一些小命令,你自己看一看,怎么执行,怎么启动gdb 是不是gdb 然后 可执行程序,那么启动起来怎么启动程序呀?是不是run 或start  那么怎么设置断点啊?

b break  怎么查看断点呢?if ib 以及设置什么自动显示啊,设置为有效无效啊,等等,以及如何结束函数的执行啊,finish  continue 等等 这些东西你自己看一下,那么人家问你的时候 你会gdb调试吗?你就说会就行了,虽然说不是太精通吧,反正他也不会让你 你给我调一个,一般也不会, 好这个gdb调试这一块,
讲了这个gbd调试之后呢,是不是给大家讲了一些io操作的一些理论啊?关于理论这一块大家重点应该理解 什么是文件描述符,你说什么是文件描述符啊? 文件描述符在哪呢?文件描述符是不是在pcb当中啊?pcb的一个文件描述符表当中,这个文件描述符表当中存放都是已经打开的文件吧?本者他是不是就是一个整型啊?一个整型值,那么内核在分配文件描述符的时候呢,有一个原则,什么原则啊?最小且未被使用的,文件描述符,

4.文件io 
这一块 咱们讲了一些函数,直接给你说函数就可以了
open  read  write  close  lseek

还讲了一些其他的接口吧?
获取文件属性lstat stat 
这两个什么区别?对于普通文件来说这两个函数是不是一样的?对于链接文件来说 lstat指向的是链接文件本身的属性,那么stat指的是 链接文件指向的文件属性吧?这个你要搞清楚,
讲了之后 还讲了一个

dup dup2 fcntl

dup dup2这两个函数的作用是,复制一个文件描述符吧?或者你说文件重定向也行,

fcntl这个是不是可以获得或设置文件的 flag 标识吧?

这个比如说我们把一个文件标识设置为非阻塞,或者设置为末尾添加,是不是都可以用这fcntl了?fcntl函数务必掌握,如果人记不住,那么你有,你知道有这么个函数,那么查的时候会查就可以了。

或者是你在人家写代码上,你去搜一下,看看人家用过这个函数没有,是不是这样的?好,

这个文件io第4天、第5天的,
是不是进程相关的?哪个?哪个?目录哪个是吧?
目录那个?是不是讲了 3 个呀、 opendir readdir  closedir
这3 个


哪个主体?那个那个是不是也讲了一个什么什么那个属性 s_mde 那个那个自己看一下就可以了, set 那个就不再说了,那个属于细节了。

我们是不是可以通过调用 set 函数能够知道这个文件到底是什么类型了?
是不是还可以知道这个文件有什么权限?对啊等等,就通过调用这个函数来获取。那么进程相关这一块,第五天讲进程,重点理解进程什么呀? 进程和程序的概念,基本概念搞清楚。还有重点讲的是fork函数,fork函数的返回值有几个,但这两个的话是不是说一个进程返回的是父子进程各返回一个,其中父进程返回的是什么值?是不是子进程pid啊 ?那么子线程返回的是0吧?。这你得知道。


好,这fork函数,那讲这个fork函数,讲这个execl函数吧?这个函数的目的什么呀?是不是在这个进程里面是不是拉起一个命令,或者是可执行程序啊?当然一个是什么呀?一个是这个execl,或者是 execlp,是不是这两个函数了?用的最多的就是两个函数,其实两函是不是用其中一个也行啊?也行,其他的函数你会行,不会没受影响,知道吗?那么这两个是用的最多的。


好,讲完这个之后重点又讲了一个函数,哪个?wait和waitpid 是不是子进程回收?这两个行程在哪调用?在父进程里面调用,父进程,调用这两个函数干什么呀?是不是回收子进程?搞清楚回收子进程,那么特别注意的是什么呀?我们在回收子进程的时候,我们一般情况下,假如说这个子进程多个子进程,我们一般是不是用waitpid啊?是吧? waitpid 它的参数和返回值,这个你要多看一看,清楚了了吗?这个多看一看。

 

这个咱们后边讲信号的时候是不又用到它了?还记得吗?讲 sick child 信号的时候是不是又用到它了?


第六天什么呀?是不是进程间通信呐?这一块我们讲的相对来说比较少,讲了 3个什么呀?
pipe是匿名管道吧?fifo 是有名管道,还有呢?内存社区
,这个就不再多说了,其中这个pipe的话大家应该知道只能用于有血缘关系的进程间通讯,这个fifo是不是都可以?mmap是不是也可以?都可以,就这个,


第七天的?第七天是不是重点?同学们,疑似难点?好开始头讲的什么呀?信号基本概念,这东西,没有一个什么确切的概念,那么你说的时候只要能够打出什么意思来就可以了,是不是能够举出几个例子来?信息号的特点是吧?什么特点?同学们,是不是简单是吧?不能携带大量信息是不是等等能说出来就可以了?还有信号的四要素 有什么四要素
,信号的编号,信号名称,信号的默认动作,以及这个信号是如何产生的,这意思吗?这么几个信号,那么也就说每个信号的产生是不是都有特定场合了?比如说我程序corde 了,是不是会产生(seigeicgv啊)?比如说 alarm函数是不是会产生 seigealarm信号啊)
等等,这些都有特定场合的,这信号你不能乱发,行了不能乱发,这是信号的四要素

,他讲之后讲了信号相关函数,
这个咱就讲的比较多了。
signal 是吧?signal,当然这个 signal 是啊,咱们先说这个kill是不是?  alarm 是吧?还有setitimer,raise,  还有什么呀? about 这两个(raise about)一般的用的不多,这两个是不是都可以用它代替是吧?还在讲这个,还有一个信号的注册吧?信号注册函数,是哪个? signal 还有sigaction,是吧?那么其中这两个函数里面建议你使用这个,是不是sigaction这个原因大家是不是?我给大家这个看这个手册的时候看过了,这个函数是不是在不同的平台上表现的行为不一样,所以你用的时候要用这个sigaction,特别是你代码需要移植的时候,比如说你在 linux上开发的,然后移植到 unit 上去执行了,如果你使用这个signal有可能会出问题,但注意不是说百分之百啊 你反正你,为了避免这种情况你就用sigaction最好了,是吧?同学们,好,还有这个,那讲完这个之后又讲最后一个,给大家讲一下,是信号的处理过程吗?是吧?关于这个信号处理过程,我重点跟大家说了一句话,要想处理信号是不是必须进入内核?对,还记得问题没?当然这里面有一个概念给大家忘了,什么呀?信号的处理机制。
什么机制?同学们是不是如果 a 信号给 b 信号发送一个信号,是不是应该是这样的? a 先给内核发,内核是不是再转给b?对,应该这样的,也就是说进程间通讯必须要通过内核,是不是包括咱们前面讲pipe  fifo mmap 是不是都这样的?都这样的?好,这信号处理过程,这个你把那个过程了解一下,那么我问一下大家这个信号处理函数到底是你用户进程调用的还是内核调用的?是不是内核调用的,那么这个 signal 也好,sigaction也好,其实它就是在给内核注册一个信号处理函数,是不是要告诉内核当这个信号发生以后,那么应该怎么去处理?也就是说言外之意应该是调用哪个函数吧?其实告诉内核的意思好讲这个之后又重点讲了一个什么呀?SIGCHLD信号,这个SIGCHLD信号的信号是不是一个非常重要的概念?那么这个信号该信号是产生条件,产生的条件,及作用?首先看一下这个信号产生条件还记得吗?子进程退出这个信号内核是不是会给它的父进程发送一个信号呀?好,这是一个,那么子进程收到了 sigstop,这会也会有吧?子进程收到了 sigcore的信号是不是也会有对等等,那作用什么呀?同学们?有什么用这个信号?那么父进程收到这个信号之后是不是就可以完成对子进程的回收?那么这样的话其实就是什么呀?告诉内核回收子进程这么一个时机,是不是这样的?这么一个时机?那否则的话你这个父进程是不是只能是什么呀?在一个循环里面不停的等待是吧?不停的回收等等,那这样的话我这个父进程就可以做其他事情。
当收到了一个 SIGCHLD信号之后,我就去回收子进程。好,那么既然回收的话,咱们再细挖一下,那么是不是有可能我们在回收的过程当中是不是会这样做?我收到一个信号,然后我连续回了好几个啊?是不是
为此的话是不是我们应该把这个回收是不是写在一个 while 1循环里面,是吧?这个,大家那个代码的话,SIGCHLD的代码,要看明白清了吗?那个代码的话要看明白那个是一个很重要的代码。
好,
 

 讲完这个之后还有一个信号集相关操作,是吧?信号集相关这一块你要求你掌握阻塞信号集和什么呀?未决觉信号极的关系。什么关系?同学们,这个未决信号集的产生是不是就是由于阻塞信号极的这个信号被阻塞的引起的?不是大部分,都这样的,没有不是的,目前来说我们见到的都这样的,是吧?同学们,这是一个,当然,是不是还有一种阻塞?是这个sigaction里面是不是有一个临时阻塞?还记得吗?( IC Max )的那个,那是临时阻塞,那意思什么呀?这个信号处理函数在执行期间?是不是这个需要阻塞某一信号?你可以加入到那个 ( IC Max )里面去,是吧?好,这个这两个关系搞清楚,再问一下我们自己能不能控制这个未决信号集啊?就你能不能使这个未决信号集这个信号的增多或者减少?不行,这个是不是外界触发的?那么但是我们是不是可以控制这个阻塞信号集啊?调用哪个函数啊?sig Pro mask,这里面我们是不是 Pro mask?当然这个信号机相关函数有一些个,首先你是不是得有一个 sogset_t set类型变量是不是?那相关的函数有哪些?sigemptyset sigaddset sigfillset sigdelset  sigprocmask sigismember 还有一个sigpending 信号集相关,是不是就这些函数?这些函数,由于函数比较多,有可能有同学记不住也没有关系。
那么你万一用得着的时候,你查一个例子或者是什么呀?查我给你写的代码,你把它看一看就知道怎么用了。那么过程是不是一般这样的?先定义,然后再初始化吧?
初始化完以后是不是要把某一信号是不是加入到这个结构当中来?然后你再调用sigprocmask,是不是设置阻塞信号集啊?一般这么干的,是不是?当然获取未决信号集调用 seek pending,对,判断某一个信号是不是在在这个集合当中调用sigismeber是吧?兄弟们,整点就是这么一过程,好信号集相关就完了。当然这落了一点。
哪点?咱们这信号是排支持排队吗?同学们,信号不支持排队,那所谓不支持排队什么意思?也就是说,假如我这个信号 a 在这个处理函数执行期间又产生了多次这样的信号,这个信号本身是不是被阻塞的?对,如果产生了多次怎么办?最后只执行一次是吧?你们,当然如果说这个信号在处理前,你把这个某另外一个信号也阻塞了,那么另外一信号也产生了多次,最后是不是也会执行一次,也只会处理一次,是这样的吗?

 

第八天守护进程和下场是吧?这个守护进程要求大家掌握,创建守护进程的模型,模型你也不用记,是吧?其实这个里面是不是这个不能省略的?只有那三步,还记得吗?同学们,是forke,然后退出,第一个吧?然后调用set_sid完事吧?最后一个可以执行核心操作,是吧?同学们,那中间那几步,什么什么umask 文件掩码,什么什么关闭文件描述,还有这个改变当前的工作目录等等,这些是不是都可以省略

创建模型我就不再多说了,你把那代码看一看,我给大家手写的代码了,把代码看一看。好,

线程
这一块要求你掌握线程的什么呀?基本概念。
我讲这基本概念的时候是不是给大家画了图,这一块你晚上的时候把这个复习

线程和进程的比较,其实这个比较严格来说就是围绕着一个中心来说的,你这个进程的话,多进程是不是他这地址空间是不一样的?你这个线程的话,是不是多个线程在同一个进程空间里面?这是它的本质,清楚了吗同学们?
它能够共享哪些?不能够共享哪些,你得知道
是不是绝大部分都可以共享,但是这个栈是不能共享的, errorno 虽然说可以共享,但是你不能用? errorno是不是一个条件变量啊?你能用,但是这个你用起来这个值不准,一般我们也不用它,我们是不是应该用那个错误号啊?还记得吗? 把那个错号给我打印出来,对应的错误原因打出来就可以了。
一般用那个

这个线程和进度比较,这个你自己想想怎么说?到时候核心是不是?我也说过了,多个线程是不是共享一个进程空间?但是多个进程的话是不是各个进程空间是相互独立的?对,是不是?那么进程空间有哪些?那么线程空间有哪些?你这个得知道,我问大家多进程父子进程能不能共享文件描述符啊?能不能。
可以?你不是写了吗?你写了个父进程,打开一个文件,一个父进程写,那么另外一个进程是不是可以读到?多进程可以,父子进程是可以共享文件名符的,那么不只是父子进程,是不是即便不是父亲生也可以吗?同学们,我 a进程和 b进程毫不毫无关系。我 a 进程打开文件,我 b 进程去读,能读吗?可以,是不是也可以?因为这个文件是不是在磁盘上啊?他们之间联系的纽带就是这个文件?这个你可以复习一下。
那我再问一下,共享映射区能不能被这个两个进程共享?可以,是不是可以?可以,这你得知道啊?你得知道。那么线程的话能不能共享文件描述呢?可以的,是可以的。当然线程的话是不是也可以共享全局变量啊?对,都可以。当然线程如果多线程,你访问共享资源要干什么呀?加锁好,然后线程相关函数,创建,然后怎么创建?这个比如说 
p_thread_create  创建
pthread_exit 退出

phtread_join等待回收


还有设置线程分离呢?pthread_detach

取消线程pthread_cancel


设置取消点  pthread_testcancel


以及一些其他的函数,有很多,咱们这两天讲的。这我写不全,你就自己看一看。

最重要的一个,咱们今天包括咱们
第8天后面讲的什么线程同步,线程同步好,互斥锁,是不是互斥锁的
使用步骤?这个我就不再多说了。这个咱们今天是不是也说过了,互斥锁怎么去使用?先定义,初始化,初始化以后要记得销毁,
然后在这个共享资源出现的位置是不是加锁,解锁,还有这个是条件,当然还有一个什么呀?读写锁,读写锁的使用步骤是吧?还有什么呀?条件?变量,信号量 ,当然这些是不是相当于咱们第9天讲的了?
对啊,这儿是吧?第九天是就这,是不是就这些?同学们就这些?好,那咱们复习的就说这么多,这个别的就没有了,咱就不说了,好吧
 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值