lesson35:
一、
1.OS调度的基本单位(0:13:5)
2.进程=XX+XX(0:14:15)
a.进程的内核数据结构包含哪几个部分?(n个)(0:15:0)
b.已初始化全局数据区和未初始化全局数据区在有些教材里面合并为XX(0:17:55)
c.XX区和XX区是整体被使用的(0:22:10)
d.堆区是被整体使用的吗?(0:22:25)
①malloc底层调用的是XX(0:23:50)
c.XX对地址空间的堆区域进行更细腻的划分(0:26:20)
①vm_area_struct仅用于对堆区进行更细腻的划分吗?
②对堆区里每次申请而形成的大量小空间,也有自己的start和end。
③如何将这些堆区的小空间联系起来?(0:28:10))(0:31:19
④对堆区申请的空间进行管理(0:29:0)(好好看看怎么讲的)
Ⅰ.当我们用申请的堆区的起始地址去访问堆区对应的小空间时,它会在哪里去搜索查找?并用什么作为key值? (0:30:35)
Ⅱ、堆区最典型的特征是什么?(0:31:0)
二、
1.MMU:
a.MMU是XX(0:37:18)
b.是集成在XX(0:37:30)
c.CPU读取的时候,读取的是XX,读取完出来的时候,到了XX(0:37:43)
d.CPU内有缓存吗?(0:38:7)
2.程序本质上是XX(0:40:25)
a.可执行程序本来就是按照XX的方式进行编译的(0:41:10)
①可执行程序还没加载到内存前,它的逻辑地址(也就是虚拟地址),就已经全部排好了。(0:41:30)
②可执行程序,其实按照区域也已经被划分成了XX(0:42:20)
Ⅰ.每个区域都只有4KB吗?(0:42:35)
3.物理内存以XXKB为单位,划分成了一个个的块儿(0:44:40)
a.操作系统进行IO的时候,是以XXKB为单位的(0:44:55)
b.物理内存是怎么完成划分的?(0:46:5)
4.4G的物理内存可以划分出100多万个4KB块儿,这么多块儿需不需要OS管理呢?(0:50:15)
a.它是通过XX的方式将所有的块儿管理起来了(0:52:45)
Ⅰ.块儿对应的编号实际上是XX(0:53:0)
b.flag选项的作用(4点)(0:53:25)
c.什么是页帧?(0:55:20)
d.什么是页框?(0:55:50)
e.IO的基本单位是4KB的理解(0:56:10)
①假如我外设加载1比特位的内容到物理内存,加载的内容是以4KB为单位吗?(0:58:5)
5.程序运行起来后,用户级页表就是建立的虚拟地址和物理地址的映射吗?(0:58:35)
a.他俩是如何对应(映射)上的?(0:58:50)
①磁盘中的所有地址都是(0:59:0)
b.页表当中还包含一些标记字段,代表什么? (0:59:20)
①假如标记字段显示,该虚拟地址对应的内容没在物理内存里,怎么办?(0:59:35)
c.什么是缺页中断?(1:0:30)
①什么是对用户透明(1:1:15)
三、
1.页表的左边要维护多少个映射关系?(1:4:30)
2.页表的K和V分别是多少字节?标记位呢?(1:5:15)
3.页表本身保存在哪里?(1:6:20)
4.页表是硬件还是软件?(1:6:23)
5.页表要维护的映射占多大空间?(1:6:40)
6.这么大的空间随页表保存在物理内存中,物理空间哪里还有多余的空间去加载其它的代码和数据?(1:8:20)
a.CPU寻址的时候,读进来的是XX,出来的是XX(1:8:40)
①怎么做到的?(1:8:55)
Ⅰ.对32比特位而言,它们是整体使用的吗?划分了几个区?(1:11:15)
b.一级页表索引的对象(即Key)是什么?(1:12:10)
①一级页表需要构建多少对映射关系?(1:12:30)
Ⅰ.2的10次方个比特位等于多少KB?(1:12:50)
②一级页表的V是物理地址吗?(1:13:35)
c.二级页表的Key是什么?(1:14:25)
①二级页表的V值是什么?(1:15:15)
d.通过一二级页表,只需要多少比特位就能找到对应在物理内存中page的起始地址start?(1:15:30)
e.二级页表的V值是对应的物理内存的物理地址吗?(1:16:10)
①那应该是什么?
②将来我在进行读写时,访问的数据一定是在XX中(1:16:40)
Ⅰ.如何访问?(1:16:44)
③页内偏移在哪里?(1:17:25)
Ⅰ.为什么表示页内偏移是12个比特位?(1:17:35~1:18:20 )
f.为什么不存在页表过大,物理空间不够的问题?(1:18:50)
①2的20次方比特位是多大?(1:19:25)
g.一级页表和二级页表全都需要开辟出来吗?(1:19:50)
①如果全部开辟,一个一级页表会创建多少个二级页表(1:21:0)
②1024X1024=100多万个4KB物理内存块儿,就是2的10次方乘以2的10次方。(1:20:50)
四、如何理解线程(1:39:0)
1.对CPU来讲,面前有5个task_struct,执行哪个对它而言有区别吗?(1:44:10)
2.这5个task_struct可以叫做什么?(1:45:0)
3.线程在进程内部执行,如何理解?(1:46:0)
4.如何理解线程是OS调度的基本单位?(1:46:20)
a.CPU关心执行流是进程还是线程吗?
5.一个单进程内的10个函数,只能按顺序依次执行。有了多线程以后,会发生怎样的变化?(1:54:20)
6.该进程对应的代码和数据是在磁盘还是物理内存?(1:56:10)
7.什么叫做进程(一个视角+理解曾经)
a.新创建的这些进程PCB还会向内存申请地址空间吗?(1:57:10)
①这些新PCB的资源来自哪里?(1:58:10)
②内核视角,XX是承担分配系统资源的基本实体(1:59:30)
b.面试官如果问什么是进程,怎么回答?(2:0:35)
c.如何理解曾经我们所写的所有代码?(2:2:0)
①以前的进程内部有几个执行流?(2:2:45)
②今天的进程内部有几个执行流?(2:2:55)
③什么叫进程?(最新说法)(2:3:30)
④task_struct是XX?(2:5:5)
8.XX是CPU调度的基本单位(2:10:5)
9.CPU视角:在Linux下,PCBXX其它OS内的PCB的(2:12:30)
a.Linux下的进程,统一称之为XX(2:14:30)
①轻量级如何体现?(2:14:35~2:15:15)
10.Linux有真正意义上的线程吗?(2:16:30~2:17:25)
a.什么是单进程?(2:18:38)
b.什么是多线程(2:18:48)
c.Linux并不能直接给我们提供线程相关的接口,只能提供轻量级进程的接口。
①为了让小白也能用,Linux在用户层实现了一套用户层线程方案,以库的方式提供给用户使用。这个库就叫做XX(2:23:0)
Ⅰ.这个库的作用(2:23:55)
五、证明线程在进程内部运行
1.pthread_create函数
a.作用(2:31:45)
b.参数
①thread(2:31:55)
②attr 默认值是什么?(2:39:33)
③void *(*start_routine) (void *) (2:32:25)
Ⅰ.作用(2:32:30)
④一旦创建线程成功,arg将会传给谁?(2:33:28)
c.返回值(2:33:55)
2.pthread是第几方库?(2:34:20)
a.什么是第一方库?2:34:30)
b.第二方库?
c.第三方库?
①你可以把它当作系统自带的吗?(2:34:40)
②第三方库属于C/C++吗?(2:34:45)
3.如何创建线程ID?(2:39:0)
a.phread_t本质上是什么类型?(2:39:10)
4.第四个参数的类型是const char*,但是函数要求第四个参数类型是void*,怎么办?(2:40:10)
pthread_create(&tid,nullptr,threadRun,"thread 1");
5.以下函数执行完毕后,"thread 1"会发生什么?(2:40:25)
pthread_create(&tid,nullptr,threadRun,(void *)"thread 1");
6.如何将实参的args用string类型的对象来接收?(2:41:5)
void* threadRun(void* args)
{
//string对象接收args
}
7.在一个线程里面获取pid,意味着什么?(2:42:10)
a.线程是在哪里运行的?(2:42:15)
b.既然线程是进程的一部分,那么在线程获取的pid是不是就是进程的pid?(2:43:0)
c.我们以前讲多进程的时候,叫父子进程。现在学多线程,我们叫主线程和新线程,没有父子概念。(2:43:55)
8.pthread_create调用的函数threadRun运行终止以后,才回到main函数执行while循环打印main thread, pid吗?
void* threadRun(void* args)
{
string name = (char*)args;
while(true)
{
cout<< name << " , pid: " << getpid() <
不是的,它俩并发执行,跟父子进程一个道理。
9.多个线程:
a.如何建立多个线程:(2:44:50~2:45:20)
b.如何将特定内容格式化成自己想要的格式,并输出到指定的地方?(2:46:40)
①它的作用是什么?(2:47:35)
snprintf(name,sizeof name,"%s-%d","thread",i);
②数组name[下标]对应的内容是什么?(2:47:40)
char name[64];
snprintf(name,sizeof name,"%s-%d","thread",i);
c.下面程序在干什么?(2:48:20)
pthread_t tid[5];
char name[64];
for(int i=0;i<5;i++)
{
snprintf(name,sizeof name,"%s-%d","thread",i);
pthread_create(tid+i,nullptr,threadRun,(void*)name);
sleep(1);
}
①sleep(1)的作用是什么?(2:49:15)
b.发生了以下的报错怎么办?
c.发生以下的报错怎么办?(2:50:30)
[qkj@VM-8-17-centos lx]$ make
g++ -o x mykill.cc -g -std=c++11
/tmp/ccQI8OuY.o: In function `main':
/home/qkj/lx/mykill.cc:26: undefined reference to `pthread_create'
collect2: error: ld returned 1 exit status
make: *** [mykill] Error 1
①红线区意味着什么?
①如何确定有没有到这个库呢?(2:51:45)
Ⅰ.如何查看这个库在哪里?(2:52:10)
Ⅱ.它对应的头文件在哪里?(2:52:55)
7.在用户层面上,能看到几个进程?(2:54:55)
8.如何查看底层给我们创建的轻量级进程?(2:55:25)
a.-L的作用(2:55:40)
b.LWP对应的英语单词是Light weight process。是轻量级进程的编号。
c.如何找到主线程?(2:56:37)
d.OS、CPU进行调度的时候,是看PID还是LWP?(2:57:0)
①那我们以前看PID是不是错了?(2:57:45 )
e.如果我们kill -9 pid会把包括主线程在内的所有线程杀掉吗?(2:58:30)
①为什么?(2:58:50)
六、线程如何看待进程内部的资源?(3:8:10~3:16:30)
1.进程内的资源一旦释放,整个线程会怎样?(3:8:10)
2.进程内部的资源在线程之间都是共享的吗?(3:8:35)
a.哪些资源是共享的?(4点+4区)(3:9:5~3:12:0)
①一个线程打开的文件描述符是3,另一个线程也打开文件,那么它分配的文件描述符也是3吗?
②堆区是共享的吗(3:12:10)
Ⅰ.实际用的时候,为什么当作私有的?(3:12:35)
③栈区是共享的吗?(3:13:15)
b.哪些是线程私有的?(3:14:0)
①一组寄存器是什么?(3:14:7)
②为什么栈是私有的?(3:14:28)
Ⅰ.栈可以被别人看到吗?(3:15:0)
七、为什么线程切换的成本更低?(3:18:50)
1.一个执行流被CPU调度时,它对应的地址空间和页表这样的字段,包括正在运行的进程的PCB,这三者对应的地址会被load进CPU的寄存器内。(3:19:40)
2.进程切换的成本更高,核心的原因是XX(3:20:18)
a.CPU内部是有L1~L3 cache。
b.CPU的一次寻址过程是缓存吗?(3:21:20)
c.实际CPU在读取指令的时候,它是读一条指令然后就把它加载到内存里吗?(3:22:0)
①什么是预读?(3:22:25)
②什么是局部性原理?(3:23:40)
d.如果进程切换,会导致XX(3:25:0)
e.为什么创建一个线程轻量化,删除也轻量化?(3:26:20)
Lesson36:
一、
1.线程创建的越多越好吗?(0:9:50)
2.创建线程数量的原则(0:13:35)
3.线程间通信(0:14:35)
4.线程导致的健壮性问题(0:15:30)
a.对多进程,一个进程挂了会影响其它进程吗?(0:16:0)
b.对多线程,一个线程挂了会影响其它线程吗?(0:16:10)
5.多进程依靠什么保证访问控制?(0:17:15)
a.多线程有访问控制吗?(0:17:35)
6.任何一个线程一旦崩溃(比如说除0),整个进程会跟着崩溃吗?剩下的其它线程呢?(0:19:40)
7.线程的用途(2点)(0:21:10)
8.fork底层调用的接口是XX(0:26:20)
9.vfork接口的作用(2点)(0:27:55)
二、
1.fork之后,父子进程谁先运行?由谁决定?(0:35:25)
a.同理,主线程和新线程创建成功之后,谁先运行呢?由谁运行?(0:35:35)
2.线程新创建
3.新线程异常,对其它线程的影响(0:42:0)
4.线程在创建并执行的时候,需要主线程等待吗?(0:42:55)
a.如果主线程不等待,会引起什么?(0:43:40)
5.pthread_join函数(0:44:40)
a.作用
b.参数
①void **retval默认设为什么?(0:45:4)
c.返回值(0:45:15)
6.主线程等待新线程
a.以下写法,默认主线程是XX等待新进程退出(0:47:50)
pthread_join(tid,nullptr);
7.常见的threadRoutine返回值类型是void*,那么返回值是什么?(0:53:18)
void *threadRoutine(void *args)
a.如果我想返回10,应该怎么办?(0:53:30)
①(void *)10意味着什么?(0:54:5)
8.threadRoutine的形参来自何处?(0:54:20)
9.threadRoutine的返回值是返回给谁呢?(0:55:0)
10.主线程如何获取到子线程的返回值:(0:56:20)
a.为什么pthread_join的第二个参数是void**?(0:56:52)
b.主线程如何提取子线程传给它的数据?(0:57:22)
①其中的ret属于指针还是指针变量?(0:58:55)
void *ret = nullptr;
Ⅰ.void* 开辟了多大的空间?(0:59:40)
Ⅱ.Linux是一个X位的机器(0:59:45)
②pthread_join的第二个参数怎么填?(1:2:5~1:2:45)
③新线程的返回值被主线程获取后,被保存在哪里了?(1:3:20)
④为什么(int)ret不行,要改为(long long)ret?(1:5:5)
c.主线程获取到子线程的返回值的代码
11.新线程内部申请一块堆空间,如何将堆空间内的数据给主线程?(1:8:0)
a.主线程接收新线程的推空间地址后,怎么打印堆空间的内容?(1:9:17)
12.为什么新线程异常的时候,主线程没有获取新线程的异常退出信息?(1:13:40)
a.线程等待是否需要关心线程的退出是否异常?(1:14:20)
13.exit提前终止线程(1:18:30)
a.为什么主线程接收线程退出信息后的cout<< "main。。。。没有执行?(1:19:20)
①为什么在多线程中,绝对不要调用exit?(1:19:40)
②不能用exit,有替代吗?(1:20:37)
Ⅰ.为什么它的参数要强转成void * ?(1:21:0)
Ⅱ.使用pthread_exit((void*)13)后,主线程得到的数据是多少?(1:22:8)
14.pthread_cancel函数
a.作用(1:22:50)
14.新线程陷入死循环,主线程让它退出
a.新线程创建成功后,线程id会保存在哪里?(1:25:5)
b.如何将陷入死循环的新线程取消?
①线程被取消,意味着该线程退出。
c.为什么新线程被取消以后,主线程获取的退出码是-1?(1:28:20)
d.假如我创建新线程以后就立马取消它,会不会有问题?(1:29:55)
e.使用pthread_cancel取消线程的前提条件?(3点)(1:32:55)
15.既然主线程可以取消新线程,那么反过来新线程是否可以取消主线程?(1:33:35)
a.可以这么干吗?(1:33:55)
b.为什么(1:34:0)
三、线程ID
1.线程id的类型pthread_t本质上是什么类型(1:48:30)
2.用printf打印时,格式怎么写?(1:48:35)
3.为什么打印出来的id不是LWP?(1:49:50)
4.我们打印出来的id本质上是什么?(1:50:30)
5.我们目前用的不是Linux自带的创建线程的接口,而是XX(1:51:30)
a.Linux层面它不关心什么?关心什么?(1:51:50)
b.内核里面创建的是XX,用户要的是XX(1:51:57)
c.用户和操作系统内核之间加了一个XX(1:52:10)
①它在哪里实现的?(1:52:15)
d.线程的管理全是操作系统承担的吗?(1:52:30)
①操作系统承担的是什么?(1:52:40)
②库承担什么?(1:52:48)
四、
1.我们调用pthread库,默认是XX链接,必须XX(1:57:40)
a.这么多线程,怎么看到这个库?(1:58:0)
①pthread库被映射到哪个区?(1:58:30)
2.如何保证你的栈区是每个执行流独占的呢?(2:1:20)
a.为什么要把一个进程的栈拆成多个栈?(2:1:45)
b.栈是操作系统提供的,还是用户层提供的?(2:2:0)
c.线程tid本质上是啥?(2:4:40)
d.既然每个线程都是在共享库里面被维护,那么还用栈区吗?(2:6:40)
①主线程用的什么栈?新线程用的什么栈?(2:6:54)
3.如何保证创建一个线程的时候,保证和主线程在同一个地址空间?还要保证给该线程传入一个共享区对应的地址来充当栈结构?(2:8:20)
a.int (*fn)(void *)是什么?(2:8:50)
b.void *child_stack 是什么?(2:9:0)
c.pthread库的底层干了什么?(2:9:40)
d.新线程打印处来的数实际上是一串地址,该地址来自XX(2:10:25)
4.新线程获取自己的线程id(2:11:0~2:11:21)
a.主线程可以用吗?(2:11:45)
5.新线程可以自己取消自己?(2:12:25)
a.这种方式推荐吗?(2:12:50)
6.一个线程可以访问另一个线程的独立栈吗?(2:14:15)
五、证明全局数据是被所有线程共享的(2:15:10~2:19:30)
六、如果我想让全局变量变成私有的,怎么办?(2:19:30)
1.__thread的作用(2:20:20)
2.__thread的功能是怎么做到的?(2:21:35)
七、线程可以调用程序替换吗?(2:23:40)
1.进程的程序替换的本质(2:24:15)
2.线程程序替换会影响其它线程吗?为什么?(2:24:30)
3.先让主线程运行起来,打印一段时间之后,再让新线程进行程序替换,会出问题吗?(2:26:20)
4.对3,为什么没有出错?(2:28:10)
5.线程进行程序替换等价于调用XX?(2:28:53)
6.任意一个线程里调用exit等价于XX(2:29:25)
7.在任意一个线程里调fork,可以吗?拷贝的是谁的?(2:29:37)
八、线程分离
1.对pthread_join,线程等待可以使用非阻塞吗?(2:31:15)
a.不想阻塞等待,怎么办?(2:31:50)
2.线程分离的好处(2点)(2:32:0)
3.我们一般采用线程分离自己,调用pthread_detach函数。
a.参数填什么?(2:33:0)
4.线程把自己分离以后,主线程还能pthread_join吗?(2:34:25)
a.主线程非要pthread_join,会发生什么?(2:34:50)(2:36:30)
5.新线程退出,出现的类似僵尸进程的状态可以看见吗?(2:38:35)
6.假如主线程比新线程先一步退出了,那么新线程的线程分离还有意义吗?(3点)(2:40:10~2:42:40)
九、家庭和个人的故事来理解进程和线程(2:44:50~2:51:30)
1.理解线程分离(2:54:0)
2.理解线程等待(2:55:0)
十、
1.C++语言有没有提供线程?(2:56:35)
2.使用C++提供的线程,在Makefile中,必须添加XX选项(3:0:15)
3.查看生成的可执行程序是否用到pthread库的指令(3:0:45)
4.为什么包括C++在内的语言支持多线程?(3:1:55)
5.语言层面的多线程,本质上是什么?(3:2:35)
十一、
1.什么是临界资源?(3:12:45)
2.什么是临界区?(3:13:50)
3.没有访问临界资源的这部分代码叫做什么?(3:14:22)
4.什么是互斥?(3:14:50)
5.什么是原子性?(3:16:20)
a.可以被打断吗? (3:16:30)
6.如果多线程访问同一个全局变量,并对它进行数据计算,多线程会相互影响吗?(3:18:20)
a.多个执行流进入同一个回调函数的现象叫做XX(3:20:10)
b.多线程抢票的逻辑(3:22:30)
int tickets = 10000;
void* getTickets(void* args)
{
(void)args;
while (true)
{
if (tickets > 0)
{
usleep(1000);
printf("%p: %d\n", pthread_self(), tickets);
tickets--;
}
else
{
break;
}
}
return nullptr;
}
①usleep(1000)是模拟什么?(3:22:40)
②else说明什么?(3:23:0)
③全局变量 tickets属于临界资源。
c.运行结果会抢到-1合理吗?(3:25:5)
d.为什么在某个时间段内,抢票的都是一个线程?(3:26:20)
7.
a.完成tickets--的动作要经历三步,分别是XX?(3:28:0)
①一旦出现线程调度,会发生什么?(3:28:40~3:29:55)
②-1是怎么打印出来的?(3:30:0~3:31:15)
③在对tickets进行XX访问的时候,导致了我们的数据不一致问题(3:31:50)
lesson37:
一、
1.线程在Linux内核当中,没有一个真正的实现体,它是用进程模拟的方式实现的。虽然操作系统没有线程,但是它依旧提供了类似线程的功能,所以它设计了轻量级进程。所以操作系统提供的接口,最多也就只能到创建轻量级进程的程度上。可是作为用户,我要用的是线程,你只能提供轻量级进程。如果要去学会使用轻量级进程,作为用户而言成本很高。故操作系统提供了用户级线程的实现,用户级线程内部维护了相关线程的核心数据。线程库提供了它自己独占的一些属性,包含线程ID,线程局部存储,独立栈结构。独占的或私有的属性中,最重要的是该线程的上下文(也就是它的寄存器数据)和该线程对应的独立栈结构。(0:6:0)
二、
int tickets = 10000;
void *getTickets(void *args)
{
(void)args;
while(true)
{
if(tickets > 0)
{
usleep(1000);
printf("%p:%d\n",pthread_self(),tickets);
tickets--;
}
else
{
break;
}
}
return nullptr;
}
1.if判断的本质也是计算吗(0:17:50)
a.谁给它判断的?(0:18:10)
b.tickets这个全局变量,经过编译链接变成二进制可执行程序,一运行后就一定在XX中(0:18:20)
a.CPU要判断tickets是否大于0,第一步是判断吗?(0:18:45)
①一个线程读取内存的数据,把它读到CPU的寄存器里,本质是XX(0:19:19)
②当前CPU执行哪个执行流,那么寄存器里放的就是XX(0:19:55)
Ⅰ.当执行流被切换时,它的上下文数据XX(0:20:0)
2.CPU能进行的计算(2种)(0:21:0)
3.if(tickets > 0):判断是在CPU内进行,数据是在内存里。所以,它一定要有一个动作XX(0:21:10)
a.是load到CPU内的哪里?(0:21:20)
b.出了什么错误,会导致10几个线程都以为读到的tickets是1而不是0,进而引起打印出-1?(0:22:(0:21:25)
①什么是并行运行?(0:22:23)
3.tickets--可能会出现什么问题?(0:25:30)
a.tickets在内存里,--是运算操作在CPU内运行,所以要把tickets--运算下去,要做的第一件事情就是XX(0:26:50)
①一共有哪三步?(0:27:50)
4.如图,线程t1在执行第3步的时候被切走了(0:29:15)
a.t1被切走时,自己在执行过程中产生的临时上下文数据会被保存下来吗?(0:29:30)
①保存的意义?(0:29:43)
②被保存的临时数据有哪些?(2点)(0:29:47)
Ⅰ.EIP是什么?(0:29:50)
③数据被保存在哪儿去了?(0:30:0)
b.t1被切走后,状态变成XX,被放到XX(0:30:10)
c.t2线程要执行的代码和t1是一样的,那么它做了什么?(0:30:30~0:35:30)
①t2的起始值是10000还是9999?(0:31:0)
②假如t2抢了5000张票后,切回t1,内存中的tickets是5000-1还是回到9999?(0:34:15)
d.这三步任意一步都有可能被打断。
三、加锁保护
1.pthread_mutex_init(0:40:50)
2.如何定义一把锁?(0:42:50)
a.pthread_mutex_t就是原生线程库提供的一个XX(0:43:45)
4.对锁进行初始化的方案(2种)(0:45:0)
a.如果这把锁定义的是全局的,或者静态定义的,则可以使用PTHREAD_MUTEX_INITIALIZER来初始化。(0:45:25)
5.加锁保护的区域是XX(0:46:55)
6.pthread_mutex_lock接口(0:47:20)
a.作用
7.怎么加锁?在哪里加锁(0:48:0)
8.锁的特点(0:49:0)
a.没拿到锁的线程呢?(0:49:17)
9.解锁的接口(0:53:30)
10.当tickets为0时,会发生什么?(0:54:10)
pthread_mutex_t mtx;
int tickets = 10000;
void *getTickets(void *args)
{
(void)args;
while(true)
{
pthread_mutex_lock(&mtx);
if(tickets > 0)
{
usleep(1000);
printf("%p:%d\n",pthread_self(),tickets);
tickets--;
}
else
{
break;
}
pthread_mutex_unlock(&mtx);
}
return nullptr;
}
a.假如某线程只加锁,不减锁,会发生什么?(0:54:50)
b.那么应该在哪里解锁?(0:55:15)
c.加锁和解锁之间的代码叫做XX(0:55:35)
11.并行变串行,会导致XX(0:57:45)
12.如何设置随机休眠sleep时间?(1:5:4)
13.加锁以后,面对某个时间段内,只有一个执行流在抢票的现象,如何改进?(1:10:30)
14.什么是加锁保护?(1:15:50)
15.如果我把锁定义在函数内部,并且没有static修饰,那么锁应当如何初始化?(1:18:35)
a.使用完毕后,必须对这把锁XX(1:20:0)
16.如何把锁传给多个线程?(1:21:15~1:30:0)
a.如何既让回调函数拿到锁又让回调函数拿到线程名?(1:24:0)
①结构体内的成员变量如何定义?(1:25:10)
②对象实例化时,是定义变量还是指针变量?(1:25:45)
Ⅰ.为什么是定义指针变量?
因为pthread_create的第四个参数是个指针类型,你要是定义的是变量,那么你用(void*)强转变量为指针变量的行为是不合法的,因为指针是8个字节,而你的结构体变量不是8个字节。
b,如何将i+1转化成string类型?(1 :27:25)
c.主线程等待多个新线程的代码怎么写?(1:29:30)
17.如何修改传入的形参是个类对象的抢票函数?(1:30:10)
a.这样正确吗?(1:31:15)
printf("%s: %d\n",td->tname,tickets);
b.回调函数中需要写delete td吗?(1:31:30)
c.如何判断申请锁是否成功?(1:33:20)
d.如何判断释放锁是否成功(1:34:50)
17.获取当前进程运行至结束所花的时间(1:37:15)
a.rand后面除的数越小,运行越快。
usleep(rand()%2000);
a.是不是线程越多,抢票所花的时间越少?(1:40:20)
四、加了锁以后,在临界区里,不会被切换吗?(1:53:15)
1.要是被切换了,会有问题吗?(1:54:0)
a.第一次理解(1:54:20)
①锁还没解开就切换,其它线程要想执行临界区代码怎么办?(1:55:20)
Ⅰ.其它线程申请锁能申请成功吗?为什么?(1:56:50)
Ⅱ.如何保证临界区中数据一致性?(1:57:30)
b.为什么不会出问题?(1:58:10)
2.线程可以不申请锁,单纯的访问临界资源吗?(1:59:50)
3.原子性的体现(2:1:40)
a.假设现在只有两个线程,在没有持有锁的线程2看来,对我最有意义的两种情况是?(2:2:15~2:4:0)
b.a可以体现出线程1持有锁期间,其它旁观线程看这个线程1的操作就是原子的。(2:4:10)
4.加锁就是串行执行吗?(2:5:50)
5.要访问临界资源,每一个线程都必须申请锁。它的前提是XX(2:8:30)
a.锁本身是不是一种共享资源?(2:9:0)
b.锁保证了tickets的安全,那么谁来保证锁的安全?(2:9:30)
①为了保证锁的安全,申请和释放锁必须是XX(2:10:20)
②谁来保证锁的安全?(2:53:20)
五、
1.大部分情况,线程使用的数据都是XX,XX的地址空间在XX中,其它线程可以获取吗?(2:13:20)
2.什么叫做共享变量?(2:13:50)
3.多个线程并发操作共享变量,会带来什么问题?(2点)(2:14:0)
4.objdump -d a.out指令的作用(2:14:35)
5.--操作是原子操作吗?(2:15:0)
a.--操作对应的三条汇编指令?
①这三步操作任意一步都有可能被切换,为解决这个问题,做了什么?(2:15:30)
6.在汇编的角度,如果只有XX,我们就认为该汇编语句的执行是原子的(2:18:40)
a.swap或者exchange汇编指令的作用?(2:19:40)
六、解释下面的含义(2:20:57)
1.lock等价于什么?(2:21:0)
2.锁在哪里?(2:21:27)
3.锁被理解为XX(2:21:30)
4.%al是什么?(2:21:50)
5.movb $0, %al是什么意思?(2:22:45)
6.在执行流的视角,是如何看待CPU上面的寄存器的?(2:23:40)
a.CPU内的寄存器的本质(2:24:25)
b.CPU内就这么一套寄存器,这套寄存器们,的空间是被所有的执行流XX(2:25:0)
①寄存器的内容呢?(2:25:40)
②当线程执行完了或者被切换了而退出时,线程就不管寄存器而直接退出吗?(2:26:15)
7.xchgb %al, mutex是什么意思?(2:28:17)
8.return 0代表什么?(2:29:10)
9.else代表什么?(2:29:16)(2:33:45)
10.进程A被切走时,会发生什么?(2:30:0)
11.线程B带着1被切走了后,线程A被切回,会发生什么?(2:32:25)
a.交换数据后,还会找到上次执行的上下文再执行之后的操作。
12.线程A挂起等待时,会发生什么?(2:34:8)
13.线程一旦被切回,首要干的两件事情:
a.第一件事情是XX(2:34:27)
b.第二件事情是XX(2:34:35)
14.return 0意味着什么?(2:35:8)
15.实际上,哪一行才是进行真正的申请锁操作?(2:35:43)
a.这个过程是将内存的数据拷贝给寄存器吗?(2:36:4)
①交换的目的(2:36:26)
16.寄存器内的1代表着什么?(2:36:45)
17.锁mtx在内存中默认保存的数是1。
18.线程A切走时,除了要保存寄存器内的数据,还要保存什么?(2:42:15)
19.EIP是什么?作用是什么?(2:42:35)
20.交换的现象是内存与%al寄存器做交换。交换的本质是什么?(2:45:10~2:46:2)
21.被挂起等待的线程A什么时候才会被唤醒?(2:52:13)
a.唤醒后执行goto lock;其实是在干嘛?(2:52:20)
22.上下文保存在哪里?(2处)(2:55:30)
七、
1.可重入这个概念针对的是XX(2:57:45)
a.什么是可重入?(2:57:52)
b.什么是可重入函数?(2:58:5)
2.什么是线程安全问题?(2:58:40)
3.常见的线程不安全情况(4个)(2:59:55)
a.什么是函数状态随着被调用,状态发生变化的函数?(3:0:3~3:0:40)
①如何统计一个函数被调用的次数?(3:0:8)
4.常见的不可重入情况(3:1:10)
5."如果一个函数是可重入的,那么它一定是线程安全",这个说法正确吗?(3:2:5)
6.常见的线程安全的情况(3点)(3:11:5)
a.如何理解“使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据”?(3:12:10)
7.函数是不可重入的,那么由多线使用时,一定会引发线程安全问题吗?(3:13:40)
8.如果一个函数中有全局变量,那么这个函数是线程安全吗?可重入的吗?(3:15:45)
a.这个问题的前提是XX(3:15:47)
9.可重入函数是线程安全的一种吗?(3:16:30)
10.线程安全一定是可重入的吗?(3:16:40)
七、死锁
1.一个执行流只能用一把锁吗?(3:19:42)
2.一种常见的死锁情况。(3:21:0~3:22:40)
3.买棒棒糖的故事理解死锁(3:23:15)
4.一个执行流一把锁也会造成死锁吗?(3:28:20)
5.死锁的定义?(3:30:22)
lesson38:
一、死锁
1.线程安全是XX要求(0:12:55)
2.死锁产生的4个必要条件(0:17:10)
a.产生了死锁,这4个条件就一定被满足了吗?(0:17:29)
b.要解决这4个问题才能避免死锁吗?(0:18:28)
c.什么是循环等待?(2种)(0:24:0)
d.没有临界资源,没有临界区,没有加锁,会产生死锁吗?(0:26:25)
3.pthread_mutex_lock()函数申请失败会怎样?(0:38:0)
4.pthread_mutex_trylock()函数的作用(0:38:45)
a.申请失败会被阻塞吗?(0:39:50)
b.破坏请求与保持条件的方法(0:40:5~0:41:10)
c.破坏循环等待条件的方法(0:43:47)
d.解决死锁问题的编码策略(2个)(0:44:47~0:46:10)
二、线程同步
1.两个不合理的现象
a.上节课的抢票问题中,出现了某个线程申请锁后能力过强,连续抢票,甚至一个人抢完的情况,请问这种行为错了吗?(0:51:20)
①它不合理的地方在于什么?(0:58:48)
Ⅰ.这种情况会导致什么?(一种计算机术语)(0:59:20)
b.买华为手机的时候第一天没货,第二天又去问,又没货....坚持问了一个月后,终于有货了,请问:它不合理的地方在于什么?(0:57:10)
①这种行为它错了吗?(0:58:25)
c.为了解决上面访问临界资源合理性的问题,引入了XX(1:6:21)
2.同步的引入,主要是为了解决什么问题?(1:7:45)
3.自习室的故事理解不合理的问题有哪些
a.解决方案(1:15:43)
①出自习室的人(1:16:28)
②在外面等的人(1:16:5)
b.什么是线程同步?(1:17:50)
4.当我们申请临界资源前,先要做XX(1:23:5)
a.要做检测的本质是XX(1:24:0)
b.结论:对临界资源的检测,一定是需要在XX之间(1:24:40~1:26:40)
①“常规情况下,我们要检测条件就绪,注定了我们必须频繁申请和释放锁”,这个说法正确吗?(1:27:40)
②有没有办法让我们的线程检测到资源不就绪的时候,XX(2点)(1:29:0)
Ⅰ.为了实现上面的效果,诞生了XX(1:32:5)
5.什么是竞态条件?(1:48:35)
6.条件变量初始化?(1:49:5)
a.如果我定义的是一个全局的或静态的条件变量,可以使用什么来初始化?(1:49:25)
b.如果我定义的是一个局部的条件变量,可以使用什么来初始化?(1:49:40)
c.定义的条件变量如何销毁?
7.如果我检测到对应的临界资源不就绪,我要让线程不要再频繁的检测,而是进行等待,应该怎么办?(1:50:15)
a.pthread_cond_timedwait和pthread_cond_wait的区别(1:51:15)
b.参数里面有互斥锁,说明XX(1:57:0)
8.等了一段时间,条件就绪了,怎么给我发通知?(1:51:55)
a.几个接口?
b.它们的区别?(1:52:25)
三、主进程按照指定顺序的方式依次唤醒执行不同任务的新线程(1:58:30)
1.我到底想写什么的思路(1:58:0~1:59:5)
2.如何让不同的线程做不同的事情(调不同的回调函数)?(2:1:40)
a.这是什么?(2:2:20)
typedef void **(func)(void *args);
b.如下:函数指针除了用数组以外,还有其它方法吗?(2:3:55)
typedef void *(*func)(void *args);
func funcs[TNUM]={func1,func2,func3,func4};
pthread_create(tids+i,nullptr,func+i);
c.这里的成员变量代表什么?(2:5:10)
class ThreadData
{
public:
string name;
func_t func;
}
d.Entry的作用(2:5:35)
e.创建对象的时候,是创建变量还是指针变量?(2:6:0)
f.构造函数怎么写?(2:6:30)
g.函数调用的时候,实参name怎么写?(2:7:20)
h.func_t怎么处理?(2:7:45)
①函数指针怎么修改?(2:8:18)
②构造函数调用的时候,第二个实参填什么?(2:11:18)
3.Entry函数里面做什么,才能实现执行不同的func函数?(2:12:5)
void* Entry(void* args)
{
//???
}
4.td保存在哪里?(2:15:40)
void* Entry(void* args)
{
ThreadData* td=(ThreadData*)args;
td->_func(td->_name);
}
a.多个线程同时调用Entry函数,一起生成td会不会产生干扰?(2:16:5)
b. td->_func(td->_name);本质上在干嘛?(2:16:15)
c.这里的td是怎么来的?(2:16:46)
①由谁来释放?(2:16:52)
5.如何控制线程唤醒顺序?(2:20:50~2:21:55)
a.他是在干嘛?(2:23:20)
pthread_mutex_destroy(&mtx);
b.我怎么让所有线程拿到的是同一把锁?(2:24:10~2:27:10)
①所有线程拿到的是同一把锁吗?(2:27:25)
c.上面我们为了让每个线程拿到同一把锁,改了大量的地方,有没有办法只改一小部分就能实现拿到同一把锁?(2:27:46)
①一般定义的同时,就顺便XX(2:28:16)
d.我想让线程先进行等待,等待的时候我让它干嘛它再干嘛,怎么实现?(2:29:50)
①默认该线程在执行的时候,...wait代码一旦被执行,当前线程会被XX(2:31:45)
pthread_cond_wait(pcond,pmtx);
Ⅰ.挂起就是阻塞吗?(2:31:48)
Ⅱ.阻塞本质上是做什么?(2点)(2:32:5)
Ⅲ.这个队列可以理解为由XX提供(2:32:30)
e.将4个线程放在指定的条件变量下等待,是谁先运行到条件变量下等待的?(2:34:25)
①后面一个一个唤醒时,我们会看到XX(2:35:10)
f.怎么去唤醒在指定的条件变量下等待的线程?(2:37:0)
①为什么参数只有条件变量,没有我想要唤醒的线程id?(2:37:40)
②它的作用是什么?(2:38:50)
while(true)
{
pthread_cond_signal(&cond);
sleep(1);
}
Ⅰ.有了它以后,每个func函数里,线程还有sleep休眠的必要了吗?(2:39:0)
g.在创建线程和唤醒线程之间添加一个sleep(5)的作用(2:40:43)
h.运行后发现运行顺序是这样的,意味着XX(2:41:45)
①还可以观察到一个现象,即XX(2:42:7)
Ⅰ.为什么?(2:42:20)
Ⅱ.这样做的意义是什么?(2:43:10)
6.如果我不要一次唤醒一个了,而是一次唤醒全部线程呢?(2:44:50)
a,现象(2:45:15)
7.目前,我们还没有用到互斥锁,那么互斥锁的作用是什么呢?(2:46:4)
a.volatile的作用
b.如何写让线程循环唤醒10次后就退出的代码?(2:47:0)
①func函数中的while循环怎么修改?(2:47:40)
c.wait一定要在XX之间进行(2:52:25)
①如何修改?(2:54:25)
②检测怎么写?(2:55:18)
Ⅰ.临界资源没有就绪怎么处理?(2:56:20~2:56:40)
d.为什么执行了10次唤醒后程序就卡住了?(2:59:30)
①如何修改?(3:0:30)
8.条件变量的作用(3:1:38)
四、生产者消费者模型
1.超市
a.超市生产产品吗?(3:16:40)
b.超市的产品从哪里来?(3:16:50)
c.供货商的角色是XX(3:17:5)
d.谁是消费者?(3:17:23)
e.超市是什么角色?(3:17:54)
f.消费者、超市、供货商的关系(3:18:7)
g.工厂一般建在哪儿?(3:18:20)
h.超市一般建在哪儿?(3:18:30)
i.为什么不直接略过超市,消费者直接找供货商购物?(3:19:20)
j.超市的作用(3:20:0)
①核心的价值是XX(3:20:37)
k.供货商只负责XX(3:21:9)
l.超市只负责XX(3:21:12)
m.k和l的过程,称之为XX(3:21:20)
n.为什么超市可以提高效率?
①超市购买供货商的商品,是超市需要吗?(3:21:45)
②超市的本质(3:21:54)
o.通过XX达到提高效率的目的(3:22:18)
p.如果火腿肠A和火腿肠B供货商同时向超市供货,那么超市选谁的问题,可以看出XX(3:23:0)
2.研究生产者消费者模型有三个阶段:
a.2种角色(3:24:50)
b.1个交易场所(3:25:5)
c.3种关系(3:27:8)
①生产者和生产者之间是什么关系?(3:28:18)
Ⅰ.在计算机里面,何谓竞争?(3:28:27)
Ⅱ.在计算机里面,竞争换一种说法就是XX(3:28:55)
②消费者和消费者的关系(3:29:38)
③生产者和消费者的关系(3:30:10)(3:33:33)
3.生产者消费者模型遵守的原则(3:34:40)
4.站在工程师的角度,生产者消费者就是XX(3:36:5)
a.什么是线程角色化?(3:36:30)
5.站在计算机角度,超市就是XX(3:36:50)
6.超市里有没有新增商品,谁最清楚?(3:37:56)
7.超市里还剩多少空间供供货商提供商品,谁最清楚?(3:38:10)
8.“条件满足时,我们唤醒指定的线程。” 我怎么知道条件是否满足呢?(3:38:30~3:39:0)
lesson39
一、生产者消费者模型补充的点
1.如果只有一个生产者和一个消费者,是不是只需要维护生产和消费的数据安全和同步,不需要维护生产者和消费者之间的互斥?(0:26:35)
2.生产消费过程只是把数据生产到仓库里,让对方在合适的时候拿走吗?(0:28:40)
a.生产者生产的数据是从哪里来的?(0:31:30)
b.消费者如何使用发送过来的数据?
①消费者使用发送过来的数据花时间吗?(0:31:50)
二、基于BlockQueue的生产者消费者模型
1.BlockQueue其实就是XX(0:32:40)
2.阻塞队列和数据结构阶段学的队列的区别(2点)(0:34:0)
a.阻塞是相对于谁来谈的?(0:34:40)
b.阻塞队列在生产者和消费者看来,就是一个XX(0:35:0)
3.一个线程往缓冲区里放数据,放满了就不放了。另一个线程往缓冲区里取数据,为空就阻塞不取了。这一性质就是和前面学过的XX一样(0:36:9)
a.进程间通信的前提(0:36:45)
b.进程间通信的本质(0:36:55)
c.管道自带XX和XX机制(0:37:7)
三、
1.STL是线程安全的吗?(0:45:28)
2.容器类接口都不是线程安全吗?(0:45:38)
a.为什么?(0:45:42~0:46:4)
b.那么STL设计出来是为了XX(0:46:5)
①线程安全问题怎么办?(0:46:10)
3.C++11支持线程,那么它支持锁和条件变量吗?(0:46:55)
a.它底层一定封装的是XX(0:47:2)
4.C++如何定义的锁?(0:47:20~0:47:50)
5.将阻塞队列写成类模板的意义(0:49:48)
6.设置容量上限的目的(0:51:46)
7.如何保证队列安全?(0:53:30~0:53:41)
a.锁是生产者和消费者共享的吗?(0:54:25)
8.如何避免生产者和消费者内耗?(0:55:13)
9.要做的互相通知,有几种条件场景?(0:55:55)
a.第一种(0:56:0)
b.第二种(0:56:4)
c.因此,需要几个条件变量?(0:56:7)
①谁最关心是否为空?(0:58:22)
②谁最关心是否为满?(0:58:33)
10.成员变量中需要用户自定义初始化(即不是在构造函数中提供的形参)哪些内容?(0:59:30)
11.阻塞队列最主要的是要提供哪几个接口?
a.第一个(1:1:39)
b.第二个(1:2:10)
12.定义对象为变量或指针都可以吗?(1:4:0)
a.定义为指针时,如何初始化?(1:4:10)
13.如何让生产者和消费者线程看到同一个阻塞队列?(1:4:55)
14.如何让生产者向阻塞队列填生产的数据?(1:6:5)
15.如何让消费者消耗阻塞队列里的数据呢?(1:6:27)
16.构造函数的函数体内需要初始化哪些内容?(1:7:42)
a.我想简化它,能怎么做?(1:8:44)
pthread_mutex_init(&_mtx,nullptr)
17.析构函数怎么写?(1:11:10)
18.Push函数
a.访问Push的本质是XX(1:12:4)
b.访问Push的前提是XX(1:12:10)
①有没有可能在生产者Push的时候,消费者在Pop呢?(1:12:26)
c.在消费的时候,是否也要考虑用加锁解锁的方式?(1:13:25)
d.这样写,正确吗?(1:14:4)
void Push(const T& in)
{
pthread_mutex_lock(&_mtx);
_bq.push(in);
pthread_mutex_unlock(&_mtx);
}
①要在临界资源中访问临界资源,先做的事情是XX(1:14:25)
e.阻塞队列判空写?(1:16:0)
f.阻塞队列是否为满的函数怎么写?(1:16:30)
g.这样修改合理吗?(1:17:0)
void Push(const T& in)
{
pthread_mutex_lock(&_mtx);
if(isQueueFull()) break;
pthread_mutex_unlock(&_mtx);
}
①怎么修改?(1:17:34)
Ⅰ.pthread_cond_wait的第一个参数填什么?(1:18:0)
②pthread_cond_wait竟然在临界区中,换句话说XX(1:20:17)
③为什么pthread_cond_wait的第二个参数设计成了一把锁(1:21:44~1:22:20)
Ⅰ.我在挂起期间自己释放可以吗?(1:23:7)
h.解释一下(1:23:40)
void Push(const T& in)//生产者
{
pthread_mutex_lock(&_mtx);
if(isQueueFull()) pthread_cond_wait(&_Full,&_mtx);
pthread_mutex_unlock(&_mtx);
}
①所谓的被挂起是什么意思?(1:24:35)
②当我被唤醒时,我从哪里醒来呢?(1:24:53)
i.当我访问临界资源的时候,意味着什么(1:26:10)
①做什么?(1:26:30)
j.当我们被唤醒的时候,pthread_cond_wait会干嘛?(1:38:10)
k.我怎么知道条件满足了,该唤醒我了?(1:39:30~1:40:20)
l.谁唤醒的生产者?(1:40:20)
①唤醒发生在解锁前还是解锁后?(1:40:50)
②如何唤醒(1:41:20)
m.生产者如何唤醒消费者?(1:42:0)
n.如果你在给我发生产或消费的条件变量的通知的时候,我没有在等待呢?(1:42:50)
19.如果我让消费的比生产的慢,应该怎么修改?(1:44:55)
a.会看到什么现象?(1:45:55)
20.如果我让生产的比消费的慢,会看到什么现象(1:47:0)
21.我希望不要再发生我生产一个,你消费一个的现象了,而是我生产了一半以后,你再消费,怎么修改?(1:48:10~1:49:30)
22.为什么发送唤醒信号可以发生在解锁之前?(1:51:36~1:5)
a.它将对方唤醒来了,此时自己还未释放锁,为什么不担心?(1:51:50)
b.如果是多个线程都在条件变量下等呢?(1:52:10)
c.如果一半的线程在条件变量下等,有一半正在运行,此时我突然解锁了呢?(1:52:22)
d.你关心是哪个线程完成的消费或者生产吗?(1:52:30)
e.如果我刚一解锁,锁就被其它线程拿走了,被唤醒的线程没拿到呢?(1:52:55)
24.Push函数这样写,还有问题吗?(1:55:25~1:55:30)
void Push(const T& in)//生产者
{
pthread_mutex_lock(&_mtx);
if(isQueueFull()) pthread_cond_wait(&_Full,&_mtx);
_bq.push(in);
if(_bq.size() >= _capacity/2) pthread_cond_signal(&_Empty);
pthread_mutex_unlock(&_mtx);
}
a.接口返回值类型为int,表明XX(1:56:12)
b.pthread_cond_wait调用失败,意味着什么?(1:56:25)
c.pthread_cond_wait可能存在XX的情况(1:57:19)
①什么是伪唤醒?(1:57:20)
d.如何应对遇到失败或者伪唤醒情况?(1:58:16)
①怎么修改?(1:58:55)
②消费者也要这么修改吗?(2:0:10)
25.回顾之前遗留的两个问题
a.“条件满足的时候,我们再唤醒指定的线程”,我怎么知道条件是否满足呢?(2:4:15)
b.mutex的意义(2:4:25)
26.我不知道生产和消费线程哪个先执行,有影响吗?(2:7:0~2:7:30)
27.解耦带来的效率提高如何体现?(2:8:38~2:13:20)
a.你把数据拷贝到缓冲区里,另一个线程将数据从缓冲区里拿的过程,效率就提高了吗?(2:8:55)
b.我生产者可以向缓冲区存很多数据,消费者可以不等生产者生产,算是提高了效率吗?(2:9:35)
c.消费者在花时间处理发送过来的数据时,生产者可以 XX(2:11:32)
①这相当于实现了两个线程的XX(2:11:48)
d.生产者在花时间从外部获取数据时,消费者可以XX(2:12:45)
28.我可以不向模板里面放int,而是放待处理的任务吗?(2:16:55)
BlockQueue* bqueue = new BlockQueue();
29.我想定义一个计算器,我生产一个计算任务,然后把任务交给消费线程,让消费线程去完成这个任务,怎么定义?(2:18:40)
a.这是什么?
typedef function func_t;
b.重载()怎么写?(0:21:20)
c.构造函数怎么写?(2:22:30)
d.如何让每个阻塞队列里放任务?(2:23:16)
30.生成随机数
a.头文件(2:24:20)
b.如何生成随机种子值?
①^getpid()^0x32457的作用?(2:24:39)
c.如何生成随机数?(3:25:20)
31.生产者如何构造一个任务?(3:26:20~0:28:20)
32.消费者如何获取一个任务?(2:29:15)
a.如何完成一个任务?(2:31:10)
33.之前都是写的单生产者和单消费者,现在怎么写多生产者和多消费者?(2:38:0)
34.多生产和多消费:
a.生产和生产,消费和消费,生产和消费之间的关系是XX(2:42:28)
b.这种关系的好处是XX(2:42:37)
c.意义(2:42:54)
d.获取任务时会并发吗?(2:44:10)
e.最耗废时间的是拿任务过程还是处理任务的过程?(2:44:50)
f.什么时候并发?(2:45:10)
g.计算由几条汇编完成?调度由几条汇编完成?(2:46:0)
①任务特简单的,需要用多线程多消费吗?(2:46:15)
②什么任务才用多线程?(2:46:20)
35.什么是线程池?(2:49:47)
四、锁的封装
1.如何定义锁?(2:54:50)
a.解释(2:58:40)
2.如何定义锁的守卫?(2:58:35)
a.解释(2:58:58)
3.怎么调整?(3:2:50)
void Push(const T& in)//生产者
{
pthread_mutex_lock(&_mtx);
if(isQueueFull()) pthread_cond_wait(&_Full,&_mtx);
_bq.push(in);
if(_bq.size() >= _capacity/2) pthread_cond_signal(&_Empty);
pthread_mutex_unlock(&_mtx);
}
a.
{
lockGuard lockguard(&_mtx);
}
①这是在干嘛?(3:1:20)
②它存在的意义?(3:2:28)
c.走出代码块儿时,会发生什么?(3:3:0)
{
lockGuard lockguard(&_mtx);
while(isQueueFull()) pthread_cond_wait(&_Full,&_mtx);
_bq.push(in);
pthread_cond_signal(&_Empty);
}
①进入这个块儿时,会发生什么?(3:3:25)
d.只要在代码块儿的头部写了lockGuard lockguard(&_mtx),则XX(3:3:50)
e.去掉代码块儿呢?(3:4:25)
4.同理,Pop函数呢?(3:5:0)
5.这种风格的加锁方式称为XX(3:5:36)
lesson40:
一、
1.
void* productor(void* args)
{
//制作任务
//生产任务
//输出消息
}
a.制作任务一定是从生产者来的吗?(2:55:10)
二、POSIX信号量
1.POSIX信号量和SystemV信号量作用相同吗?(3:6:55)
a.作用是什么?(3:6:55)
2.信号量的类型是什么?(3:7:30)
3.如何定义信号量?(3:7:34)
4.信号量的初始化函数?(3:7:45)
5.pshared为0表示什么?非0表示什么?(3:8:0)
a.默认条件下,设为XX(3:8:6)
6.信号量本质上就是个XX(3:8:45)
a.信号量的初始值是几由XX决定(3:8:50)
7.如何销毁信号量?(3:9:0)
8.什么是p操作?什么是v操作?(3:9:20)
a.PV操作的本质是XX(3:9:30)
二、什么是信号量
1.保证共享资源在任何一个时刻都只有一个执行流在访问,引出了XX和XX和概念(3:10:55)
2.生产者消费者模型中,访问缓冲区这部分资源是XX的(3:12:0)
a.对临界资源进行互斥访问,意味着XX(3:12:40)
3.如果针对一份资源,不同的线程作用的对象是该资源的不同区域,对我们而言,如果采用互斥的方案,将整个空间当作整体来访问,那么带来的结果是XX(3:14:25)
a.正确的方案是XX(3:14:35)
4.什么情况下用并发?什么情况下用互斥或同步?(3:16:25)
5.你怎么保证这个资源就是给你的?(3:18:20)
6.我怎么知道我一定可以具有一个共享资源呢?(3:18:30)
三、电影院的故事
1.你真正坐在座位上,座位资源才真正属于你吗?(3:20:0)
2.买票的本质(3:21:5)
3.电影院有100个座位,它会卖超过100张票吗?(3:21:58)
4.信号量就是个计数器,访问临界资源的时候,必须先申请信号量资源吗?(3:22:55)
a.申请信号量资源,本质上是对信号量XX(3:23:13)
b.使用完毕信号量资源,本质上是对信号量XX(3:23:45)
c.如果我申请了一个信号量资源,却又不去访问该资源对应的空间,会出现什么问题?(3:24:50)
①申请一个信号量资源如同XX(3:25:37)
②对信号量++就是在XX(3:26:5)
③预定资源叫做XX操作,释放 资源叫做XX(3:26:20)
5.申请一个信号量,代表着XX(3:28:0)
a.是用哪一个资源呢?(3:28:40~3:29:35)
6.你怎么知道一共有多少个资源?还剩多少资源(3:29:55)
7.我怎么知道我一定可以具有一个共享资源呢?(3:30:25)
lesson41:
一、
1.信号量是用来描述XX的计数器(0:2:25)
2.信号量是临界资源当中,资源的XX(0:2:40)
3.信号量要保证3点(0:3:38)
二、基于环形队列的生产消费模型
1.它和基于阻塞队列的生产消费模型的本质区别是XX(0:5:50)
2.什么是环形结构?(0:7:10)
a.如何实现该结构?(0:8:10)
b.该结构是物理结构还是逻辑结构?(0:9:10)
①真实的物理结构是长这样的么?(0:9:13)
②真实的物理结构长啥样?(0:9:25)
c.假设index为数组下标,如何处理才能让index到达下标为尾部的n时,再++,就回到首部下标为0的位置?(0:10:25)
①为什么%n+1?
因为数组的下标比实际数少1,尾部下标为n,表明实际数组个数为n+1,%n+1就确保了取值范围为0~n
d.使用环形结构时,要进行XX和XX(0:12:15)
e.在企业项目中,遇到了一些没听过的结构,如果以后你的leader告诉你,把一个什么什么结构看作是什么什么结构,你首先要想到的是XX(0:19:5~0:19:45)
f.未来演示或者思考的时候,是基于逻辑结构还是物理结构?(0:22:40)
①为什么要把物理结构抽象成逻辑结构?(0:23:5)
g.在环形结构中,先放数据再移动,还是先移动再放数据?(0:24:20)
h.在多线程当中,如果一直填数据移动直到重新回到起点,还能再填数据吗?(0:26:5)
i.写入位置会指向最开始的两种情况(0:27:30)
3.什么是基于环形结构的生产消费模型?
a.一定有XX位置和XX位置(0:27:50)
b.刚开始的时候,环形队列为空意味着什么?(0:28:20)
c.随着不断向环形队列里放数据,本质上是XX往后移动(0:28:30)
d.当生产者下标回到最开始的位置,和消费者下标同位置,生产者还能再生产吗?(0:29:0)
e.消费者永远指向起始位置吗?(0:29:44)
f.队列为空或为满的判断条件都是XX(0:30:15)
①这样的缺点是XX(0:30:28)
g.如何区分到底是空还是满?(2种)
①第一种(0:31:40)
②第二种(0:32:30)
4.消费者线程XX数据,生产者线程XX数据(0:36:29)
5.环形结构是多线程下的共享资源吗?(0:37:40)
6.环形结构可以被局部使用吗?(0:39:30)
7.在环形结构中,队列为空和为满:
a.为空和为满时,消费者线程和生产者线程指向同一个位置吗?(0:41:30)
b.为空的时候,XX线程不运行(0:41:38)
c.为满的时候,XX线程不运行(0:41:43)
8.生产和消费指向环形结构同一个位置,该位置一定是起点吗?(0:43:25)
a.从数据结构的角度看:如果生产者和消费者指向了环形结构的同一个位置,则XX(0:43:35)
b.从多线程角度看:~,则XX(0:44:15)
①什么是互斥?(0:44:28)
c.生产和消费指向同一个位置是大概率事件吗?(0:45:27)
d.大部分情况下,是什么情况?(0:45:47~0:46:15)
e.想让生产和消费指向同一个位置,需要具有XX(0:46:35)
f.想让生产和消费不指向同一个位置,需要让他们XX(0:46:55)
9.4个原则(0:53:0)
a.生产者可以将消费者套圈吗?(0:48:40)
b.消费者可以超过生产者吗?(0:49:35)
c.为空的时候,一定要让XX先运行(0:51:40)
d.为满的时候,一定要让XX先运行(0:52:35)
e.其它情况呢?(0:53:40)
10.生产者最关心的是XX(0:57:40)
a.因为生产者最关心空间资源,因此我们定义一个XX(0:59:20)
①用XX来表示计数器(0:59:30)
②该计数器的起始是XX?(1:0:25)
Ⅰ.起始为N意味着XX(1:0:30)
11.消费者最关注的是XX(0:58:30)
a.为什么dataSem的起始值为0?(1:1:8)
12.生产线程如何保证此次生产过程一定成功?(1:2:15)
a.生产了一个数据放在了特定的空间里,完毕之后,这个空间依旧是被占用的吗?(1:5:25)
①该过程中,数据资源多了一个,如何表示?(1:6:20)
13.你想要进行消费,必须先XX(1:7:7)
a.进行完P操作以后,数据被拿走了,数据资源依旧少了一个,但是曾经这个数据资源占据的空间资源就XX(1:8:5)
14.最开始的时候,生产消费之前,我们要先XX(1:9:55)
a.生产消费线程同时过来的时候,要让谁先运行?(1:10:50)
①消费线程会发生什么?(1:10:18)
Ⅰ.P操作申请失败,会发生什么?(1:10:27)
Ⅱ.为什么?(1:10:31)
b.生产线程有将空间资源释放的能力吗?(1:11:37)
c.当生产线程申请信号量失败,即空间资源申请完的时候,生产者线程会被XX(1:12:19)
①这个情况下,消费者会XX(1:12:52)
15.如何让生产者和消费者在不同的位置上的时候,可以同时进行生产和消费?(1:16:55)
a.不为空或不为满意味着什么?(1:17:25)
三、单生产单消费的环形队列模型
1.环形队列里面要有哪些成员变量?(1:26:0)(2:6:22)
a.int num需要写吗?(1:26:15)
2.不确定模拟环形队列的数组将来要存放哪种数据,故将类写成XX(1:26:55)
3.环形队列里面得有哪些接口?(1:28:15)
4.如何表示定义了生产线程和消费线程?(1:29:0)
5.为什么生产者要用while(true)来生产数据?(1:42:48)
a.它怎么生产?
①第一步(1:43:20)
Ⅰ.数据或任务对象可以从外部来吗?
Ⅱ.可以忽略它的时间消耗问题吗?(1:44:0)
②第二步(1:44:25)
Ⅰ.push完以后,就相当于XX(1:44:35)
b.消费者的消费过程?
①第一步(1:45:10)
②第二步(1:45:30)
Ⅰ.可以忽略它的时间消耗问题吗?(1:45:35)
6.构造函数:
a.为什么值给这么小?(1:48:30)
const int g_defult_num = 5;
b.实现(1:49:20)
①_ring_queue(default_num)的作用?(1:49:0)
c.构造函数如何初始化空间资源和数据资源?(2:7:20 )
7.这里初始化的时候,填进去的参数的作用是什么?
RingQueue* rq=new RingQueue(?);
定义size大小
8.Push函数是XX使用,Pop函数是XX使用(1:52:50)
9.Push函数
a.这样写,正确吗?(1:53:35)
void Push(const T& in)
{
int index=0;
_ring_queue[index++]=in;
index %=_num;
}
①怎么修改?(1:54:10)
Ⅰ.这样修改的缺陷(1:55:8)
b.如何加入P操作?(2:8:20)
c.如何加入V操作?(2:10:0)
10.补充的两个成员变量分别是(1:56:10)
a.它俩最开始的时候,初始值一定是XX(1:56:55)
b.如何修改Push函数?(1:57:15)
c.这种写法有什么问题?(1:58:20)
void Push(const T& in)
{
_ring_queue[p_step++]=in;
p_step%=_num;
}
①需要引入XX,来对它进行保护(1:58:38)
11.信号量:
a.相关的头文件是XX(1:59:45)
b.初始化信号量的函数(2:0:40)
c.构造函数和析构函数(2:2:39)
d.sem_wait的作用(2:3:20)(2:4:0)
①P操作怎么写?(2:4:25)
e.sem_post的作用(2:4:35)
①V操作怎么写?(2:5:15)
12.Pop函数怎么写?(2:13:15)
13.生产者和消费者下标相等的时候,我们不知道谁先运行怎么处理怎么办?(2:12:3)
14.二元信号量在PV操作时,就是XX(2:15:0)
a.什么是多元信号量?(2:15:5)
①它用来表示XX(2:15:10)
b.从严格意义上来说,它是加锁吗?(2:15:14)
15.随机数
a.对应的头文件?(2:16:5)
b.time函数对应的头文件?(2:16:30)
c.getpid函数对应的头文件?(2个要一起用才能识别!!!)(2:17:4)
d.如何让生产者随机生成0~100的数?(2:17:30)
16.如果消费很慢,那么看到的现象是什么?(2:20:0)
a.为什么消费一个生成一个的时候,它俩的数据不一样?(2:20:20~2:20:40)
b.改变sleep的位置,到最开始的位置,现象是什么?(2:21:10)
①有并发访问吗?(2:21:18)
17.如果生成很慢,那么看到的现象是什么?(2:22:55)
a.不管是谁快还是谁慢,在最开始时,一定是XX先运行(2:21:40)
18.我们可以在当前的单生产和单消费代码当中,维护好生产者与消费者的互斥与同步关系,即当生产者和消费者指向同一个位置的时候,我们不担心它俩会对同一个位置做并发访问,因为XX(2:24:30~2:25:20)
四、如何将单生产单消费的环形队列模型修改为多生产多消费的环形队列模型?
1.多生产和多消费与单生产和单消费相比,多维护了什么?(2:26:48~2:27:5)
a.生产者和生产者、消费者和消费者是什么关系?(2:27:16)
①故我们势必是要XX(2:27:24)
2.生产者和消费者可以并发执行吗?(2:27:30)
a.请问是在什么情况下可以并发执行?(2:27:35)
①我们能不能只是并发的大量的去让生产和消费只加一把锁?(2:27:45~2:28:10)
b.我们现在只是维护生产和生产、消费和消费的关系,故可以用XX?(2:28:16)
①这两把锁怎么分配?(2:28:19)
3.生产者和生产者之间是互斥关系的本质原因是XX(2:28:45)
a.请问生产者们的临界资源是什么?(2:30:10)
4.成员变量添加什么?(2:30:55)
5.构造函数怎么写?(2:31:25)
6.析构函数怎么写?(2:31:50)
7.如何给生产者们加锁?(2:32:20)
8.消费者们加锁减锁也是用的plock参数吗?(2:32:56)
9.运行到红色箭头部分意味着什么?(2:33:30)
a.函数内部,一定是XX(2:34:35)
b.只有申请锁成功的线程,才能竞争XX(2:35:20)
c.信号量本身是XX(2:36:15)
d.加锁的本质(2:36:20)
e.信号量一定是安全的吗?(2:37:46)
①为什么?(2点)(2:37:49)
f.先加锁还是先申请信号?(2:38:10)
①如果先加锁,那么申请信号量的线程一定非常多吗?(2:38:45)
Ⅰ.整个系统的工作效率是否高,取决于XX(2:38:59)
②为什么先申请信号后申请锁会提高效率,你光有信号没有锁,不还得等着吗?(2:39:55~2:40:32)
Ⅰ.这个效率类似于由先排队进电影院,再排队买票;改为先并发竞争买票再排队进电影院。
10.如何简化?(2:42:25)
pthread_join(c[0],nullptr);
pthread_join(c[1],nullptr);
pthread_join(c[2],nullptr);
11.最后生产和消费的时候,还是只有一个线程在生产和消费,那么我为什么要创建这么多线程呢,生产消费各自一个不好么,连加锁解锁都省了,那么多生产多消费的意义在哪里?(2:45:55)
a.把任务或者数据放在交易场所,就是生产和消费吗?(2:46:43)
b.生产消费过程,最废时间的是XX(2:47:15)
c.生产的本质(2:47:30)
d.消费的本质(2:47:45)
e.拿任务的时候可以并发吗?(2:47:55)
f.处理任务的时候,可以并发吗?(2:48:7)
类似于去食堂排队打饭,消费就是将公共的饭菜打到你的盘子里(私有),排队消耗的时间没有吃饭时间长,你在吃饭的时候,别人也在吃
g.多生产多消费的意义(2:48:47)
12.信号量的本质是一把计数器,计数器的意义是什么?
a.计数器是用来表征什么的?(2:49:45)
b.我们要访问临界资源之前,要先XX(2:50:10)
①访问完临界资源,要XX(2:50:13)
②在加锁和解锁之间,要XX(2:50:18)
Ⅰ.要访问临界资源,得先XX(2:50:23)
c.判断临界条件是否成立,成立,继续访问;不成立,如果只是单纯的互斥,则解锁;这样会引发什么问题?(2:51:0)
①如何解决?(2:51:15~2:51:40)
d.为什么之前我们必须按照“申请锁 -> 判断与访问 -> 释放锁”这套流程来写呢?(2:52:53)
e.信号量要提前预设资源的情况吗?(2:53:35)
f.信号量在pv变化过程中,我们在外部就能知晓临界资源的情况吗?(2:54:0)
g.计数器的意义是什么?(2:54:10)
①在代码中的体现(即为什么不需要用条件变量)(2:54:45~2:56:0)
五、线程池
1.什么是池化技术?(3:7:45)
a.申请空间要调用系统接口吗?(3:8:23)
b.调系统接口只是简单的调系统接口吗?(3:8:30)
①要做什么?(3:8:38~3:9:15)
②扩容的时候,是我们要多少就给多少吗?(3:9:30)
Ⅰ.多出来的那部分空间是干嘛的?(3:9:40~3:10:0)
c.预先申请空间、执行流的本质都是XX(3:10:55)
d.内存是资源,线程本质是XX,也是XX(3:11:8)
e.为什么得有线程池?(3:12:35)
f.什么是线程池?(3:12:50)
2.如何封装线程?
a.我想让线程执行什么函数,怎么处理?(3:20:30)
①函数对象的返回值类型定义在哪里?(3:20:0)
②函数对象的参数类型定义在哪里?(3:20:5)
③函数对象如何定义?(3:20:10)
b.线程有哪些成员变量?(3:20:50)
c.如何在调用构造函数的时候,给线程加编号?(3:23:50)
d.线程对外要提供的接口是XX(3个)
①第一个(3:24:28)
Ⅰ.我成员变量的函数_func不能作为回调函数传入pthread_create怎么办?(3:28:40)
②第二个(2:29:10)
③第三个(0:30:0)
3.线程池
a.线程池就是XX线程(3:30:31)
b.如何表示一堆线程(3:31:30)
①类模板的类型填什么?(3:31:38)
c.线程池需要提供哪些接口?(2个)
①第一个(3:32:40)
②第二个(3:32:50)
d.线程池本质是XX(3:34:30)
①故一旦有任务,需要把任务存起来,因此需要用XX(3:34:40)
lesson42:
一、封装线程
1.这样写正确吗?(0:11:30)
typedef void* (fun_t)(void*);
a.它的等价写法(0:11:43)
2.能不能把在start接口接收的回调函数和参数放在构造函数里?(0:12:35)
3.成员变量新添哪一个?(0:13:0)
二、线程池
1.线程池构造的时候,需要被告知需要多少线程吗?(0:14:5)
2.如何在线程池的构造函数中创建多个线程对象?(0:16:55)
3.析构函数怎么写?(0:18:5)
a.在调用delete之前,还需要做什么?(0:18:50)
4.如何在run接口中启动所有的线程?(0:19:55)
5.如何让线程在调用start接口,执行任务的时候,把名字也带上? (0:23:15)
a.如何修改线程的成员变量(0:23:30)
b.如何修改线程的构造函数?(0:24:0)
c.如何修改start接口(0:25:5)
6.未来,线程得到参数的时候,它的所有参数都放在XX(0:26:0)
7.routine函数实际上只有args这一个参数吗?(0:33:35)
void* routine(void* args)
{
ThreadData* td=(ThreadData*)args;
while(true)
{
std::cout<<"我是一个线程,我的名字是:"<
a.可能会引发报错吗?(0:33:50)
①它报的什么错?(0:41:28)
b.为什么只能让它有args这一个参数?(0:34:18)
c.如何让它只有args这一个参数(0:34:35)
8.如何将线程名由0、1、2改成1、2、3?(0:43:40)
9.线程池的本质是一个生产消费模型,现在我要向线程池队列当中塞任务,怎么塞?(0:46:55)
a._task_queue既被生产者使用,又被消费者使用,所以必须保证它的安全问题,还要考虑生产者和消费者有多个,他被对象整体使用,故需要XX(0:48:55)
①如何拿到锁?(0:50:5)(0:51:30)(0:52:10)(0:52:30)
②如何给生产者加锁?(0:53:40)
b.前面是单纯完成了加锁,那么需要唤醒吗?(0:55:40)
①唤醒需要在成员变量中引入XX(0:56:10)
Ⅰ.条件变量的申请放在XX(0:56:25)
Ⅱ.条件变量的释放放在XX(0:56:45)
②唤醒线程放在XX(0:57:16)
c.pushTask接口代表的是XX过程(0:57:30)
10.新线程调用routine接口,代表的是XX(0:59:5)
11.如下,在静态成员函数中直接使用 _task_queue.pop();可以吗?(1:1:50)
static void* routine(void* args)
{
ThreadData* td=(ThreadData*)args;
_task_queue.pop();
while(true)
{
std::cout<<"我是一个线程,我的名字是:"<
a.为什么?(1:1:55)
b.我routine接口无法直接使用类内的成员变量,也就无法读取任务队列,那我应该如何进行消费呢?(2种)
①第一种(1:3:55)
Ⅰ.缺点(1:4:23)
②第二种
Ⅰ.可以直接在构造函数里面将this指针传给回调函数吗?原因?(1:10:20~1:11:40)
ThreadPool(int thread_num=g_thread_num)
:_num(thread_num)
{
for(int i=0;i<_num;i++)
{
_threads.push_back(new Thread(i,routine,this));
}
pthread_mutex_init(&lock,nullptr);
pthread_cond_init(&cond,nullptr);
}
Ⅱ.它的作用是什么?(1:9:0~1:9:30)
ThreadPool* tp = (ThreadPool *)td->_args;
12.消费的逻辑(1:13:18)
a.静态方法可以直接访问类成员吗?(1:17:24)
b.我要给她加锁解锁,怎么访问成员变量里的锁?(1:17:30)
c.我想知道它有没有任务,需要访问XX(1:18:35)
①我不能直接访问task_queue怎么办(1:18:45)
②它的作用(2点)(1:22:5)
pthread_cond_wait(&cond,&lock);
d.怎么获取任务?(1:24:25~1:25:30)
①获取任务的本质(1:25:50)
e.代码区中的代码块其实相当于包含了一个XX(1:28:47)
static void* routine(void* args)
{
ThreadData* td=(ThreadData*)args;
ThreadPool* tp = (ThreadPool *)td->_args;
while(true)
{
T task;
{
lockGuard lockguard(tp->getMutex());
while(tp->isEmpty()) tp->waitCond();
//读取任务
task = tp->getTask();
}
task();
}
}
①task()的作用(1:29:15)
f.任务这个头文件应该包含在线程池文件里吗?(1:31:30)
①怎么改?(1:32:15)
int main()
{
ThreadPool* tp = new ThreadPool();
tp->run();
return 0;
}
13.主线程承担的是XX过程(1:44:35)
a.正常情况下,主线程要从网络里读取数据,读取到数据后,再将数据制作成任务扔给线程池,让它帮我们去处理。(1:45:5)
①主线程与这一批新线程池之间构成XX(1:45:20)
b.如何推送任务到线程池中(1:50:5)
c.如何启动线程池?(1:50:18)
14.定义线程池的成员变量的第二种方案(2:3:10~2::)
a.生产队列生产了一批任务之后,就XX(2:5:15)
①交换之后,c_queue指向XX;p_queue指向XX(2:5:20)
b.交换之后,执行XX(2:5:20)
c.当消费者处理完毕后,进行XX(2:6:10)
d.因为生产和消费用的是不同的队列,未来我们要进行资源的处理的仅仅是XX(2:6:50)
e.一些策略
①第一个(2:6:55~2:7:40)
Ⅰ.加锁的地方仅仅是XX(2:7:36)
②第二个(2:7:40~2:8:10)
三、封装日志
1.日志是有XX的(2:11:38)
a.不同的日志级别代表的是不同的XX(2:12:16)
2.封装日志的接口,其实就是XX(2:14:40)
3.啥意思?(2:16:0~2:16:22)
void logMessage(int level,const std::string& info)
{}
a.level是日志等级,info是日志内容。
4.完整的日志功能,至少有XX(3条)(2:18:10)
5. . . .是什么?(2:19:50)
void logMessage(int level,const std::string& info,const char* format, ...)
{}
a.作用是什么?(2:20:5)
b.可以将中间的日志内容删去,然后用格式控制来处理,放在. . .里吗?(2:20:50)
c.如果想用可变参数,则需要使用XX(2:21:38)
①它们的头文件是XX(2:21:59)
6.va_list本质上是XX(2:24:16)
7.last代表什么?(2:24:48)
a.这是啥意思?(2:24:57)
va_start(ap,format);
8.va_arg的作用(2:25:6)
a.没有参数时,返回值为XX(2:25:29)
b.加个while循环的作用(2:25:35)
9.va_end的作用(2:25:44)
10.这些va_xx函数本质上都是XX(2:27:0)
11.va_start的作用(2:27:15)
12.它们的作用(2:28:45~2:29:50)
a.vprintf对应XX(2:29:5)
b.vfprintf对应XX(2:29:7)
c.vsprintf对应XX(2:29:8)
d.vsnprintf对应XX(2:29:10)
e.它们和stdio.h下的那几个输出函数的区别?(2:29:17)
13.如何将其简化?(2:31:20)
void logMessage(int level,const char* format, ...)
{
va_list ap;
va_start(ap,format);
int x=va_arg(ap,int);
va_end(ap);
}
a.va_start的作用是XX(2:30:14)
①初始化完毕以后呢?(2:30:27)
b.如何格式化可变参数列表的起始地址?(2:30:35)
14.函数调用logMessage的时候,怎么给参数?(2:32:35)
a.它俩打印有区别吗?(2:33:47)
logMessage(NORMAL,"%s%d%c%f","这是一条日志",1234,'c',3.14);
logMessage(NORMAL,"%s %d %c %f","这是一条日志",1234,'c',3.14);
15.如果我不想打印到显示器里了,而是打印到字符串当中,怎么修改?
a.要使用哪个函数?(2:34:55)
b.怎么修改?(2:37:15)
16.如何构建一张数字与日志等级的映射表?(2:39:50)
a.stdBuffer是什么?(2:40:20)
b.logBuffer是什么?(2:40:25)
c.标准部分要有哪些内容?(2个)(2:40:38)
d.日志等级转字符串已经解决了,那么时间呢?(2种)
①第一种(2:40:59)
②第二种(2:41:15)
Ⅰ.tm是啥?(2:43:5)
Ⅱ.time_t是啥?(2:43:49)
Ⅲ.time()里面的参数是啥?(2:45:2)
③怎么修改?(2:48:10)
④如何将标准部分和自定义部分打印到显示器上(2:49:10)
17.如何将日志写在线程池里的run接口?(2:49:40~2:50:50)
a.如何写在任务里的operator()接口?(2:53:10)
①__FILE__和__LINE__是啥?(2:54:0)
Ⅰ.什么是预处理符?(2:54:30)
c.如何写在制作任务中?(2:55:50)
d.time_t输出的时候,填类型怎么填?(2:57:50)
e.为什么打印的时候出现了乱码?(3:0:10)
18.这是啥?(3:7:0~3:7:30)
void logMessage(int level,const char* format, ...)
{
#ifdef DEBUG_SHOW
if(level == DEBUG) return;
#endif
//...
}
a.如果我不想在源代码定义宏,怎么办?(3:6:30)
①定义后,定义会发生什么变化(3:6:45)
18.如果未来写代码时,有一些是常规的日志显示,有些就是你想打印的信息,可是一旦加了太多的打印代码,最后就会变成你自己都不知道你的打印代码在哪里,怎么解决?(3:8:35~3:9:30)
19.我不想把信息打印到显示器上了,而是文件里,怎么办?(3:10:30~3:12:10)
四、线程安全的单例模式
1.什么是单例模式(3:15:10)
2.常见的2种单例模式(3:15:25)
3.为什么要有单例(3:15:30~3:16:20)
4.什么是饿汉模式?(3:16:30~3:17:20)
a.应用场景(3:17:29)
b.data在什么时候被创建好?(3:18:10~3:18:25)
template
class Singleton
{
static T data;
public:
static T* GetInstance()
{
return &data;
}
};
5.什么是懒汉模式?(3:18:30)
a.懒体现在哪里?(3:18:40)
b.应用场景(3:18:50)
c.懒汉加载其实就是XX(3:19:5)
d.懒汉创建单例的方式(3:19:13)
lesson43:
一、将线程池改成懒汉单例模式
1.如果我想有一个单例模式,首先要做的是XX(0:10:40)
a.该对象是变量还是指针?(0:11:10)
b.该指针是属于类对象的吗?(0:11:35)
c.该指针是Thread*还是ThreadPool* ?(0:11:55)
d.如何定义并初始化一个该静态的指针?(0:13:15)
2.为什么叫做单例?(0:13:30)
a.构造函数怎么处理?(0:14:34)
①为什么要设为私有?(0:14:40)
c.拷贝构造如何处理?(0:15:20)
d.赋值运算符怎么处理?(0:16:32)
e.都处理了,我怎么使用线程池的单例?(0:17:8)
①如果静态指针为空,则表明XX(0:18:12)
Ⅰ.怎么处理?(0:18:30)
Ⅱ.如果我想指定参数获取呢?(0:18:55)
3.我想获取单例对象,只能通过XX(0:20:26)
a.第一次获取的时候是为空的吗?(0:20:33)
①第二次、第三次呢?(0:20:37)
b.对象只有一份吗?(0:20:45)
c.该接口不能获取该静态对象怎么办?(0:21:35)
d.获取单例的时候,在哪里写代码?怎么写?(0:23:35)
①如何简化访问单例线程池?(0:26:30)
Ⅰ.推送任务到线程池呢?(0:26:45)
4.单例可以把构造也写成=delete吗?(0:27:25~0:27:35)
5.红框框处可以不写吗?(0:27:40)
6.如果单例本身也在被多线程申请使用呢?(0:29:15)
a.多个线程从何而来?(0:29:40)
b.当前代码是线程安全的吗?(0:32:16)
①什么情况下,这样写是没有问题的?(0:32:30)
c.为什么要用单例呢?(0:32:50)
①为什么系统的对象可能会存在多份?(2种)(0:33:0)
d.怎么做修改?(0:33:20)
①怎么创建锁?(0:33:55)
Ⅰ.静态锁可以用宏那种方式初始化吗?(0:34:25)
Ⅱ.如何定义锁?(0:37:25)
②怎么对该接口的指定部分进行保护?(0:35:5~0:35:50)
e.这种写法,保证了线程安全,但是还存在什么问题?(2个)
static ThreadPool* getThreadPool(int num = g_thread_num)
{
{
lockGuard lockguard(&mutex);
if (nullptr == thread_ptr)
{
thread_ptr = new ThreadPool(num);
}
pthread_mutex_unlock(&mutex);
}
return thread_ptr;
}
①第一个(0:41:35)
Ⅰ.可以并发调用吗?(0:41:45)
②第二个(0:42:55)
Ⅰ.thread_ptr指针什么情况下才为空?(0:43:23)
Ⅱ.第一个线程获取后,其它线程再去获取,还可能为空吗?(0:43:35)
Ⅲ.我们担心的是第XX次的竞争(0:43:40)
③对于一个已经安全的资源,线程还需要排队吗?(0:44:15)
④如何调整?(0:44:55)
Ⅰ.这样写的好处?(2点)(0:46:5~0:46:35)
Ⅱ.在外部添加if判断的好处?(2点)(0:46:45)(0:47:30)
二、STL、智能指针和线程安全
1.STL中的容器是否是线程安全的?(0:49:59)
2.STL设计的初衷(0:58:5)
a.故后续在多线程的情景下使用STL时,有关容器的线程安全问题需要XX(0:50:20)
3.主流的三种智能指针(0:50:30)
4.智能指针是否是线程安全的?(0:50:)
a.unique_ptr的作用域?
b.unique_ptr涉及线程安全吗?(0:51:2~0:51:20)
①unique_ptr在任何情况下都不需要做保护吗?(0:51:25~0:51:41)
b.智能指针的两种使用场景(0:51:46)
①多线程场景下需要加保护吗?(0:52:0)
c.shared_ptr共享资源吗?(0:52:10)
d.unique_ptr一旦定义好了,一定只在一个线程的上下文当中被使用吗?(0:52:30)
e.shared_ptr允许指针之间相互拷贝吗?(0:53:2)
①这个特点会出现什么问题?(0:53:10)
②unique_ptr会出现这个问题吗?(0:53:20)
f.shared_ptr内部引用计数是原子性的吗?(0:54:30)
①但是有什么问题?(0:54:36)
②我们使用智能指针访问目标对象的方法,这个方法本身一定是线程安全的吗?(0:54:54)
③我获取对象的对应计数,对这个智能指针本身做拷贝,会有问题吗?(0:55:0)
④我们使用智能指针,只是使用智能指针本身吗?(0:55:10)
g.如何使用智能指针?(0:55:27)
5.总结:(0:55:35~0:55:50)
三、其它常见的各种锁
1.我们目前学的锁是XX(0:56:15)
2.什么是悲观锁?(0:57:5)
a.我们之前学的互斥锁和二元信号量,都属于XX(0:57:49)
3.什么是乐观锁?(0:58:13)
a.乐观锁其实是一种XX方案(0:58:35)
b.Mysql如何做到多个并发的事务请求过来的时候,让部分读写并发起来?(0:59:15)
4.cas操作其实就是XX(1:0:18)
a.cas对应的英文单词是:compare and swap。(1:0:25)
5.cas操作的作用:
a.更新数据前,它会XX(1:0:33)
①如果相等XX(1:0:38)
②如果不相等(1:0:40)
③失败后会发生什么?(1:0:43)
三、自旋
1.如何保证临界区的安全?(1:2:43~1:3:13)
2.我们在持有锁期间,有新的线程过来了,那么新的线程就被挂起(阻塞)了,本质上其实是XX(1:3:28~1:4:10)
3.自习室学霸张三的故事(1:4:30~)
a.张三让你等他1个小时,你会啥都不干就跑他楼下等他吗?(1:7:40)
①正常情况下,你会怎么做?(1:8:8)
b.如果张三让你等它一分钟,你还会跑网吧,然后等它下来了给你打电话吗?(1:10:22)
c.是什么决定了我在楼下等的策略?(1:11:44)(1:13:25)
①张三其实就是我的XX(1:11:52)
②张三在班上同学眼里,就是XX(1:12:4)
③张三这个临界资源就绪的时间,决定了我等他的策略。(1:12:20)
Ⅰ.如果临界资源就绪的时间非常长,那么其它线程应该XX(1:12:50)
Ⅱ.如果临界资源就绪的时间非常短,那么其它线程应该XX(1:13:3)
d.如果张三让你等它一分钟,结果你等了他一分钟后,他没下来,此时你会怎么办?(1:15:4)
①结果你前前后后打了十几次电话,他都说马上下来,结果都没下来,最终你给张三打了最后一个电话,他说下来了下来了,都看到你了,你穿着红色的上衣,但其实你穿的是蓝色的上衣,张三还是没下来,你又重复给它打电话,为什么你就不走呢?(1:16:5)
②那你为什么又重复给他打电话呢?(1:16:12)
③打电话在现实的2个意义(1:16:30)
④隔一会儿打电话本质上是什么?(1:16:43)
⑤以上的场景叫做XX(1:16:55)
⑥他一直这样拖拖拉拉的,你能把它怎么样吗?(1:17:20)
4.什么是自旋?(1:17:34)
5.自旋锁的本质(1:18:10)
a.自旋锁会挂起进程吗?(1:18:39)
6.自旋锁能实现XX和XX(1:19:12)
7.什么时候使用自旋锁呢?(1:19:25~1:19:50)
8.自旋锁
a.自旋对应的英语单词(1:21:40)
b.头文件
c.定义(1:21:48)
d.初始化和销毁函数(1:21:55)
e.加锁(1:22:10)
f.解锁(1:22:25)
9.对于互斥锁,如果资源不就绪,你的线程就被挂起,请问这个挂起动作是谁做的?(1:22:55)
10.对于自旋锁,如果资源不就绪,你就自旋,请问自旋的过程是你写个while循环去做的吗?(1:23:10)
11.如果我想把之前的抢票改成自旋锁,怎么改?(1:24:4)
四、读者写者问题
1.生产消费模型要遵守321原则,那么读者写者问题也有类似的吗?(1:25:50)
a.哪3种关系?(1:26:25)
b.哪2种角色?(1:26:30)
①读者或写在只能是一个吗(1:26:34)
c.1是啥?(1:26:40)
①场所是啥?(1:26:47~1:27:5)
2.黑板报的故事
1.假设只能由一个人去写黑板报,现实生活中就真的只有一个人写吗?(1:29:5)
2.如果只在同一个区域写,还能多人一起吗?(1:29:15)
3.不考虑并发访问的问题,写者和写者的关系是XX(1:29:30)
4.当正在写入时(即还没有黑板报画完),如果立马让读者来读,会出现什么问题?(1:31:5)
a.读者和写者之间的关系是XX(1:30:34)
①当写者正在写的时候,读者XX
②当读者正在读的时候,写者XX(1:32:30)
b.写者写完的时候,碰巧放寒暑假,没有读者,开学后写者又把它擦掉了,即写者的黑板报没有人来读,如果没人读,写者的行为毫无意义,为了解决这个问题,故读者和写者之间还有XX关系(1:33:10)
c.会不会发生一群同学去看黑板报,看的时候,只有一个在睁眼看,其余人闭眼?(1:33:55)
①故我们可知,读者和读者之间的关系是XX(1:34:10)
Ⅰ.读者和读者共享关系其实是没关系,对吗?(1:34:30)
3.读者写者问题与生产消费模型的本质区别?(1:44:35)
4.让读者实现共享,写者实现互斥的伪代码:
a.读者:
①如何表示当前的读者个数(1:46:10)
②进来的时候,需要读者申请锁吗?(1:46:35)
③退出的时候,怎么做?(1:46:50)
④总结:(1:47:0~1:47:20)
b.写者:
①写者也是申请锁进来的时候,就先加锁,后++再解锁吗?(1:47:50)
Ⅰ.如果读者数>0,则XX(1:48:35~1:49:10)
Ⅱ.反之,如果没有读者,则XX(1:49:24)
c.它俩的互斥关系是咋维持的?(1:49:45~1:50:35)
d.读者的加锁过程对应伪代码的XX(1:51:10)
e.写者的加锁过程对应伪代码的XX(1:51:12)
f.它俩的解锁是一样的吗?(1:51:20)
g.加锁是要分XX加锁的(1:51:27)
h.未来创建了两线程,一个读,一个写,你要做的就是XX(1:51:40~1:52:10)
①如果你是读者(1:52:15)
②如果你是写者(1:52:40)
5.如果读者和写者同时到来都申请锁了,那我应该让谁先申请锁呢?(1:54:)
a.什么是读者优先、写者优先问题(1:54:)
①什么是生产和消费的地位是对等的?(1:54:48)
Ⅰ.为什么尽量要地位对等?(1:54:55~1:55:10)
b.只要有读者,写者就不能写,对吗?(1:55:35)
①会造成什么问题?(1:55:55)
Ⅰ.这是正常的吗?(1:56:0)
Ⅱ.为什么?(1:56:6~1:57:5)
Ⅲ.举个例子(1:57:30)
c.如果读者一直来,难道就让写者一直饥饿吗?(1:58:24)
①一般默认的情况下,都是XX优先(1:58:35)
②什么是读者优先?(1:58:40~1:59:10)
③能不能让读者正在读的时候,让写者来写呢?(1:59:20)
④写者优先如何体现?(2:0:30)
什么是