获得徽章 0
#### 进程上下文切换有哪些场景?
- 为了保证所有进程可以得到公平调度,CPU 时间被划分为一段段的时间片,这些时间片再被轮流分配给各个进程。这样,当某个进程的时间片耗尽了,进程就从运行状态变为就绪状态,系统从就绪队列选择另外一个进程运行;
- 进程在系统资源不足(比如内存不足)时,要等到资源满足后才可以运行,这个时候进程也会被挂起,并由系统调度其他进程运行;
- 当进程通过睡眠函数 sleep 这样的方法将自己主动挂起时,自然也会重新调度;
- 当有优先级更高的进程运行时,为了保证高优先级进程的运行,当前进程会被挂起,由高优先级进程来运行;
- 发生硬件中断时,CPU 上的进程会被中断挂起,转而执行内核中的中断服务程序;
- 为了保证所有进程可以得到公平调度,CPU 时间被划分为一段段的时间片,这些时间片再被轮流分配给各个进程。这样,当某个进程的时间片耗尽了,进程就从运行状态变为就绪状态,系统从就绪队列选择另外一个进程运行;
- 进程在系统资源不足(比如内存不足)时,要等到资源满足后才可以运行,这个时候进程也会被挂起,并由系统调度其他进程运行;
- 当进程通过睡眠函数 sleep 这样的方法将自己主动挂起时,自然也会重新调度;
- 当有优先级更高的进程运行时,为了保证高优先级进程的运行,当前进程会被挂起,由高优先级进程来运行;
- 发生硬件中断时,CPU 上的进程会被中断挂起,转而执行内核中的中断服务程序;
展开
评论
点赞
## linux 下 g++编译程序时-I(大写i) 与-L(大写l)-l(小写l) 的作用详解
**(1) -I **(寻找头文件)
编译程序按照-I指定的路进去搜索头文件。
`-I/home/include/`表示将`-I/home/include/`目录作为第一个寻找头文件的目录,寻找的顺序是:
> /home/include/ -->/usr/include-->/usr/local/include
**(2)-L**(寻找库文件)
表示:编译程序按照-L指定的路进去寻找库文件,一般的在-L的后面可以一次用-l指定多个库文件。
> -L/lib/表示到/lib/目录下找库文件
**3)-l(小写L)**
表示:编译程序到系统默认路进搜索,如果找不到,到当前目录,如果当前目录找不到,则到`LD_LIBRARY_PATH`等环境变量置顶的路进去查找,如果还找不到,那么编译程序提示找不到库。
本例子使用的是gunzip库,库文件名是libz.so,库名是z。很容易看出,把库文件名的头lib和尾.so去掉就是库名了。
**(1) -I **(寻找头文件)
编译程序按照-I指定的路进去搜索头文件。
`-I/home/include/`表示将`-I/home/include/`目录作为第一个寻找头文件的目录,寻找的顺序是:
> /home/include/ -->/usr/include-->/usr/local/include
**(2)-L**(寻找库文件)
表示:编译程序按照-L指定的路进去寻找库文件,一般的在-L的后面可以一次用-l指定多个库文件。
> -L/lib/表示到/lib/目录下找库文件
**3)-l(小写L)**
表示:编译程序到系统默认路进搜索,如果找不到,到当前目录,如果当前目录找不到,则到`LD_LIBRARY_PATH`等环境变量置顶的路进去查找,如果还找不到,那么编译程序提示找不到库。
本例子使用的是gunzip库,库文件名是libz.so,库名是z。很容易看出,把库文件名的头lib和尾.so去掉就是库名了。
展开
评论
点赞
### hashmap为什么不用b+树而用红黑树
HashMap使用红黑树来解决哈希冲突的问题,而没有选择B+树。
这是因为在哈希表中,查找操作的平均时间复杂度应尽可能低,而红黑树在保证平衡的同时,能够在O(log n)的时间复杂度内完成插入、删除和查找操作。 相比之下,B+树在查询方面并没有明显的优势。虽然B+树在范围查询上更有优势,但在HashMap中,查找操作更加频繁,对单个元素的访问更为重要。因此,使用红黑树更加合理。
另外,B+树的数据结构比红黑树更为复杂,实现和维护的复杂度也较高。HashMap中的哈希桶(bucket)是一个链表或红黑树的数组,每个桶是一个独立的数据结构,相互之间没有关联。而B+树是一个完整的树结构,节点间需要维护指针,增加了额外的开销和复杂性。
综上所述,为了在HashMap中实现快速的查找操作,并且减小实现和维护的复杂度,选择红黑树来解决哈希冲突问题是合理的选择。
HashMap使用红黑树来解决哈希冲突的问题,而没有选择B+树。
这是因为在哈希表中,查找操作的平均时间复杂度应尽可能低,而红黑树在保证平衡的同时,能够在O(log n)的时间复杂度内完成插入、删除和查找操作。 相比之下,B+树在查询方面并没有明显的优势。虽然B+树在范围查询上更有优势,但在HashMap中,查找操作更加频繁,对单个元素的访问更为重要。因此,使用红黑树更加合理。
另外,B+树的数据结构比红黑树更为复杂,实现和维护的复杂度也较高。HashMap中的哈希桶(bucket)是一个链表或红黑树的数组,每个桶是一个独立的数据结构,相互之间没有关联。而B+树是一个完整的树结构,节点间需要维护指针,增加了额外的开销和复杂性。
综上所述,为了在HashMap中实现快速的查找操作,并且减小实现和维护的复杂度,选择红黑树来解决哈希冲突问题是合理的选择。
展开
评论
点赞
### **虚函数怎么实现的?**
虚函数是一种C++中的特殊函数,它允许在派生类中重写基类中定义的同名函数。虚函数的实现涉及到一些复杂的机制,包括虚函数表和虚函数指针。
在C++中,每个对象都有一个指向其类的虚函数表的指针(称为vptr),该虚函数表包含该类及其所有基类的虚函数的地址。当调用对象的虚函数时,编译器会使用vptr来查找虚函数表中的函数地址,并将控制权转移到正确的函数。
具体来说,当创建一个对象时,编译器会在该对象的内存布局中分配一个指向虚函数表的指针。该虚函数表由编译器自动生成,并包含该类及其所有基类的虚函数的地址。当调用虚函数时,编译器会使用vptr来查找虚函数表中的函数地址,并将控制权转移到正确的函数。
需要注意的是,只有类中声明为虚函数的函数才会被添加到虚函数表中。如果派生类重写基类中的虚函数,它将覆盖虚函数表中的基类函数地址,并添加自己的函数地址。如果派生类没有重写虚函数,则将使用基类中定义的函数。
虚函数的实现涉及到编译器的内部机制,包括虚函数表和虚函数指针。这些机制为C++中的多态性提供了强大的支持,使得派生类可以重写基类中定义的函数,并根据需要自定义其行为。
虚函数是一种C++中的特殊函数,它允许在派生类中重写基类中定义的同名函数。虚函数的实现涉及到一些复杂的机制,包括虚函数表和虚函数指针。
在C++中,每个对象都有一个指向其类的虚函数表的指针(称为vptr),该虚函数表包含该类及其所有基类的虚函数的地址。当调用对象的虚函数时,编译器会使用vptr来查找虚函数表中的函数地址,并将控制权转移到正确的函数。
具体来说,当创建一个对象时,编译器会在该对象的内存布局中分配一个指向虚函数表的指针。该虚函数表由编译器自动生成,并包含该类及其所有基类的虚函数的地址。当调用虚函数时,编译器会使用vptr来查找虚函数表中的函数地址,并将控制权转移到正确的函数。
需要注意的是,只有类中声明为虚函数的函数才会被添加到虚函数表中。如果派生类重写基类中的虚函数,它将覆盖虚函数表中的基类函数地址,并添加自己的函数地址。如果派生类没有重写虚函数,则将使用基类中定义的函数。
虚函数的实现涉及到编译器的内部机制,包括虚函数表和虚函数指针。这些机制为C++中的多态性提供了强大的支持,使得派生类可以重写基类中定义的函数,并根据需要自定义其行为。
展开
评论
点赞
什么是信号,信号量是如何实现的?
信号:
在Linux中,为了响应各种事件,提供了几十种信号,可以通过kill -l命令查看。
如果是运行在 shell终端 的进程,可以通过键盘组合键来给进程发送信号,例如使用Ctrl+C 产生SIGINT 信号,表示终止进程。
如果是运行在后台的进程,可以通过命令来给进程发送信号,例如使用kill -9 PID 产生SIGKILL信号,表示立即结束进程。
信号量:
当使用共享内存的通信方式,如果有多个进程同时往共享内存写入数据,有可能先写的进程的内容被其他进程覆盖了。
因此需要一种保护机制,信号量本质上是一个整型的计数器,用于实现进程间的互斥和同步。
信号量代表着资源的数量,操作信号量的方式有两种:
P操作:这个操作会将信号量减一,相减后信号量如果小于0,则表示资源已经被占用了,进程需要阻塞等待;如果大于等于0,则说明还有资源可用,进程可以正常执行。
V操作:这个操作会将信号量加一,相加后信号量如果小于等于0,则表明当前有进程阻塞,于是会将该进程唤醒;如果大于0,则表示当前没有阻塞的进程。
信号:
在Linux中,为了响应各种事件,提供了几十种信号,可以通过kill -l命令查看。
如果是运行在 shell终端 的进程,可以通过键盘组合键来给进程发送信号,例如使用Ctrl+C 产生SIGINT 信号,表示终止进程。
如果是运行在后台的进程,可以通过命令来给进程发送信号,例如使用kill -9 PID 产生SIGKILL信号,表示立即结束进程。
信号量:
当使用共享内存的通信方式,如果有多个进程同时往共享内存写入数据,有可能先写的进程的内容被其他进程覆盖了。
因此需要一种保护机制,信号量本质上是一个整型的计数器,用于实现进程间的互斥和同步。
信号量代表着资源的数量,操作信号量的方式有两种:
P操作:这个操作会将信号量减一,相减后信号量如果小于0,则表示资源已经被占用了,进程需要阻塞等待;如果大于等于0,则说明还有资源可用,进程可以正常执行。
V操作:这个操作会将信号量加一,相加后信号量如果小于等于0,则表明当前有进程阻塞,于是会将该进程唤醒;如果大于0,则表示当前没有阻塞的进程。
展开
评论
点赞
2.1 CSP 模型?
CSP 模型是“以通信的方式来共享内存”,不同于传统的多线程通过共享内存来通信。用于描述两个独立的并发实体通过共享的通讯 channel (管道)进行通信的并发模型。
2.2 GPM 分别是什么、分别有多少数量?
G(Goroutine): 即 Go 协程,每个 go 关键字都会创建一个协程。
M(Machine):工作线程,在 Go 中称为 Machine,数量对应真实的 CPU 数(真正干活的对象)。
P(Processor): 处理器(Go 中定义的一个摡念,非 CPU),包含运行 Go 代码的必要资源,用来调度 G 和 M 之间的关联关系,其数量可通过 GOMAXPROCS() 来设置,默认为核心数。
M 必须拥有 P 才可以执行 G 中的代码,P 含有一个包含多个 G 的队列,P 可以调度 G 交由 M 执行。
2.3 Goroutine 调度策略
队列轮转:P 会周期性的将 G 调度到 M 中执行,执行一段时间后,保存上下文,将 G 放到队列尾部,然后从队列中再取出一个 G 进行调度。除此之外,P 还会周期性的查看全局队列是否有 G 等待调度到 M 中执行。
系统调用:当 G0 即将进入系统调用时,M0 将释放 P,进而某个空闲的 M1 获取 P,继续执行 P 队列中剩下的 G。M1 的来源有可能是 M 的缓存池,也可能是新建的。
当 G0 系统调用结束后,如果有空闲的 P,则获取一个 P,继续执行 G0。如果没有,则将 G0 放入全局队列,等待被其他的 P 调度。然后 M0 将进入缓存池睡眠。
CSP 模型是“以通信的方式来共享内存”,不同于传统的多线程通过共享内存来通信。用于描述两个独立的并发实体通过共享的通讯 channel (管道)进行通信的并发模型。
2.2 GPM 分别是什么、分别有多少数量?
G(Goroutine): 即 Go 协程,每个 go 关键字都会创建一个协程。
M(Machine):工作线程,在 Go 中称为 Machine,数量对应真实的 CPU 数(真正干活的对象)。
P(Processor): 处理器(Go 中定义的一个摡念,非 CPU),包含运行 Go 代码的必要资源,用来调度 G 和 M 之间的关联关系,其数量可通过 GOMAXPROCS() 来设置,默认为核心数。
M 必须拥有 P 才可以执行 G 中的代码,P 含有一个包含多个 G 的队列,P 可以调度 G 交由 M 执行。
2.3 Goroutine 调度策略
队列轮转:P 会周期性的将 G 调度到 M 中执行,执行一段时间后,保存上下文,将 G 放到队列尾部,然后从队列中再取出一个 G 进行调度。除此之外,P 还会周期性的查看全局队列是否有 G 等待调度到 M 中执行。
系统调用:当 G0 即将进入系统调用时,M0 将释放 P,进而某个空闲的 M1 获取 P,继续执行 P 队列中剩下的 G。M1 的来源有可能是 M 的缓存池,也可能是新建的。
当 G0 系统调用结束后,如果有空闲的 P,则获取一个 P,继续执行 G0。如果没有,则将 G0 放入全局队列,等待被其他的 P 调度。然后 M0 将进入缓存池睡眠。
展开
评论
点赞
Go 语言原生 map 并不是线程安全的,对它进行并发读写操作的时候,需要加锁。而 sync.map 则是一种并发安全的 map,在 Go 1.9 引入。
sync.map 是线程安全的,读取,插入,删除也都保持着常数级的时间复杂度。
sync.map 的零值是有效的,并且零值是一个空的 map。在第一次使用之后,不允许被拷贝。
有什么用
一般情况下解决并发读写 map 的思路是加一把大锁,或者把一个 map 分成若干个小 map,对 key 进行哈希,只操作相应的小 map。前者锁的粒度比较大,影响效率;后者实现起来比较复杂,容易出错。
而使用 sync.map 之后,对 map 的读写,不需要加锁。并且它通过空间换时间的方式,使用 read 和 dirty 两个 map 来进行读写分离,降低锁时间来提高效率。
sync.map 是线程安全的,读取,插入,删除也都保持着常数级的时间复杂度。
sync.map 的零值是有效的,并且零值是一个空的 map。在第一次使用之后,不允许被拷贝。
有什么用
一般情况下解决并发读写 map 的思路是加一把大锁,或者把一个 map 分成若干个小 map,对 key 进行哈希,只操作相应的小 map。前者锁的粒度比较大,影响效率;后者实现起来比较复杂,容易出错。
而使用 sync.map 之后,对 map 的读写,不需要加锁。并且它通过空间换时间的方式,使用 read 和 dirty 两个 map 来进行读写分离,降低锁时间来提高效率。
展开
评论
点赞
写屏障(Write Barrier)
为了避免GC的过程中新修改的引用关系到GC的结果发生错误,我们需要进行STW。但是STW会影响程序的性能,所以我们要通过写屏障技术尽可能地缩短STW的时间。
❝造成引用对象丢失的条件:
一个黑色的节点A新增了指向白色节点C的引用,并且白色节点C没有除了A之外的其他灰色节点的引用,或者存在但是在GC过程中被删除了。以上两个条件需要同时满足:满足条件1时说明节点A已扫描完毕,A指向C的引用无法再被扫描到;满足条件2时说明白色节点C无其他灰色节点的引用了,即扫描结束后会被忽略 。
写屏障破坏两个条件其一即可
破坏条件1:Dijistra写屏障
满足强三色不变性:黑色节点不允许引用白色节点 当黑色节点新增了白色节点的引用时,将对应的白色节点改为灰色
破坏条件2:Yuasa写屏障
满足弱三色不变性:黑色节点允许引用白色节点,但是该白色节点有其他灰色节点间接的引用(确保不会被遗漏) 当白色节点被删除了一个引用时,悲观地认为它一定会被一个黑色节点新增引用,所以将它置为灰色
为了避免GC的过程中新修改的引用关系到GC的结果发生错误,我们需要进行STW。但是STW会影响程序的性能,所以我们要通过写屏障技术尽可能地缩短STW的时间。
❝造成引用对象丢失的条件:
一个黑色的节点A新增了指向白色节点C的引用,并且白色节点C没有除了A之外的其他灰色节点的引用,或者存在但是在GC过程中被删除了。以上两个条件需要同时满足:满足条件1时说明节点A已扫描完毕,A指向C的引用无法再被扫描到;满足条件2时说明白色节点C无其他灰色节点的引用了,即扫描结束后会被忽略 。
写屏障破坏两个条件其一即可
破坏条件1:Dijistra写屏障
满足强三色不变性:黑色节点不允许引用白色节点 当黑色节点新增了白色节点的引用时,将对应的白色节点改为灰色
破坏条件2:Yuasa写屏障
满足弱三色不变性:黑色节点允许引用白色节点,但是该白色节点有其他灰色节点间接的引用(确保不会被遗漏) 当白色节点被删除了一个引用时,悲观地认为它一定会被一个黑色节点新增引用,所以将它置为灰色
展开
评论
点赞
Channel 是同步的还是异步的?
Channel 是异步进行的, channel 存在 3 种状态:
nil,未初始化的状态,只进行了声明,或者手动赋值为 nil
active,正常的 channel,可读或者可写
closed,已关闭,千万不要误认为关闭 channel 后,channel 的值是 nil
Goroutine 和线程的区别?
一个线程可以有多个协程
线程、进程都是同步机制,而协程是异步
协程可以保留上一次调用时的状态,当过程重入时,相当于进入了上一次的调用状态
协程是需要线程来承载运行的,所以协程并不能取代线程,「线程是被分割的 CPU 资源,协程是组织好的代码流程」
Channel 是异步进行的, channel 存在 3 种状态:
nil,未初始化的状态,只进行了声明,或者手动赋值为 nil
active,正常的 channel,可读或者可写
closed,已关闭,千万不要误认为关闭 channel 后,channel 的值是 nil
Goroutine 和线程的区别?
一个线程可以有多个协程
线程、进程都是同步机制,而协程是异步
协程可以保留上一次调用时的状态,当过程重入时,相当于进入了上一次的调用状态
协程是需要线程来承载运行的,所以协程并不能取代线程,「线程是被分割的 CPU 资源,协程是组织好的代码流程」
展开
评论
点赞