python多线程的缺点
python的多线程的问题:GIL导致PYTHON 无法使用到计算机的多核,仅能使用单核
JAVA传统的多线程主要解决的问题:
1、运行于多核CPU上,各线程可分布于CPU的各个核心,让程序真正的并发
2、因为外设(IO外设)的速度不匹配,导致线程阻塞。所以需要多线程切换来让阻塞的线程让出CPU,让其它线程运行。
python线程被人诟病的主要原因:
由于python设计之初,没预料到多核cpu能够得到现在的发展,只考虑到了单核cpu。为了更好的实现多线程之间数据完整性与状态同步,于是设计出了一个全局解析器锁(GIL, global interpreter lock)。
GIL确保Python进程一次运行一个线程(其它线程处于等待I/O或者睡眠状态),无论当前cpu有多少核心。这就意味着Python虽然可以实现多线程,但是在任意时间点仅有一个核心在执行Python指令(即线程无法并行运算),无法发挥现代多核cpu的性能。
CPython解析只允许拥有GIL才能运行程序。
理解GIL的弊端
write代表互斥锁,作用是防止多个线程一起修改某一共享数据data导致错误
1)Th1和Th2的下一步都要希望对data进行修改;
2) Th1在某一时刻同时拿到了GIL与write,准备对data进行修改;
3) Th1在准备修改前,发生了I/O请求,所以需要让出GIL(如果由于I/O请求让出GIL,该线程不参与GIL的竞争,如果由于持有时间到达上限,可参与竞争),但是write依旧才Th1手上;
4) Th2获得了GIL,但是由于缺少write也无法对data进行修改,达到持有GIL时间上限后,让出GIL,并参与竞争;
5) Th1的I/O完成后,参与GIL竞争(由于Th1持有write,所以无论Th2获得多少次GIL都无法对data进行修改,而持有GIL等待write的时间将会被浪费);
6) 直到Th1获得GIL后,由于Th1拥有GIL、write,所以对data进行修改,然后释放write,进行下一步操作,直到GIL持有时间达到上限或者进程结束,释放GIL(不持有GIL无法运行);
7) Th2获得Th1释放的write,等待GIL,获得后,修改data,直到GIL持有时间达到上限或者进程结束,释放GIL(不持有GIL无法运行);加粗样式
进程,线程和协程
进程拥有自己独立的堆和栈,既不共享堆,亦不共享栈,进程由操作系统调度。
线程拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程亦由操作系统调度(标准线程是的)。
协程和线程一样共享堆,不共享栈,协程由程序员在协程的代码里显示调度。
协程和线程的区别是:协程避免了无意义的调度,由此可以提高性能,但也因此,程序员必须自己承担调度的责任,同时,协程也失去了标准线程使用多CPU的能力。
进程,协程的上下文切换
cpu保存上一次的任务状态,加载下一个任务这个过程为一次上下文切换
协程
协程的切换其实就是cpu 寄存器内容特别是%rip 和 %rsp 的写入和恢复,因为cpu 的寄存器决定了程序从哪里执行(%rip) 和使用哪个地址作为堆栈 (%rsp)
现有协程库,是怎么去实现context切换的呢,目前主要有以下几种方式:
使用ucontext系列接口,例如:libtask
使用setjmp/longjmp接口,例如:libmill
使用boost.context,纯汇编实现,内部实现机制跟ucontext完全不同,效率非常高,后面会细讲,tbox最后也是基于此实现
使用windows的GetThreadContext/SetThreadContext接口
使用windows的CreateFiber/ConvertThreadToFiber/SwitchToFiber接口
线程
线程上下文切换的原因
1.正常时间片完了之后的正常调度。
2.执行任务时遇到IO阻塞,挂起当前线程加载下一个线程。
3.多个任务抢占锁资源,当前任务没有抢到锁资源,被调度器挂起,继续下一任务。
4.用户代码挂起当前任务,让出CPU时间。
减少上下文切换
1.无锁编程:
2.CAS:原子类就是啦。
3.协程:单线程里面实现调度多任务。
线程和进程的实现方式
线程
Java多线程实现的方式有四种
1.继承Thread类,重写run方法
2.实现Runnable接口,重写run方法,实现Runnable接口的实现类的实例对象作为Thread构造函数的target
3.通过Callable和FutureTask创建线程
4.通过线程池创建线程
前面两种可以归结为一类:无返回值,原因很简单,通过重写run方法,run方式的返回值是void,所以没有办法返回结果
后面两种可以归结成一类:有返回值,通过Callable接口,就要实现call方法,这个方法的返回值是Object,所以返回的结果可以放在Object对象中
进程的通信方式
1-1、无名管道(pipe);
1-2、有名管道 (fifo);
1-3、信号(signal);
2-1、共享内存(share memory);
2-2、消息队列(message queue);
2-3、信号灯集(semaphore set);
以上6种进程间通信方式是用在本地一台计算机的不同进程间通信,而 Socket和Streams支持不同主机上的两个进程IPC, 即可用于本地一台计算机的不同进程间通信,但更多的是用于不同主机通过网络来通信。
3、套接字(socket)