因为在python设计出来的年代根本就没有多核这一说法,都是单核cpu,因为线程是cpu执行的最小单位,在单核情况下,我们的python进程中就算开了多条线程,在同一个时刻也只有一个线程被cpu调度执行,当某一个线程在执行时,其他线程都是停止工作的,所以不会同时对一个数据进行操作导致数据混乱。
但是随着发展,出现了多核cpu,那么多个cpu就能执行多条线程,而我们的python解释器是一个进程,在进程中,线程是数据共享的。假设现在cpu1运行线程1,cpu2运行线程2,如果线程1与线程2正好都需要对同一个变量进行操作,就会产生并发数据不安全的问题。(上面不会出现数据安全问题是因为只有一个cpu,也就只有一条线程在运行。不会出现多条线程同时操作一个数据)。所以龟叔想了一个办法,就在cpython解释器上加了一把互斥锁,也就是GIL,在cpython解释器中,必须要获得这把互斥锁,线程才能被调度执行,这就又保证了在同一情况,只有拿到锁的那个线程会被运行,保证了数据安全,也就是只有一个cpu在运行。这就是大家诟病的cpython慢的原因,因为GIL锁是互斥锁,只能被一个线程获得,那么cpu再多也没用了,因为抢不到锁就得等着,这就是cpython无法利用多核优势的原理。
后来龟叔一看,这样性能太差不行啊,所以他允许开多进程,开了多个进程那么就有多个python解释器进程,可以有多个GIL锁,也就可以有多个线程同时执行,并且因为进程之间是数据隔离的,也就不会产生数据混乱的问题。(首先进程间数据隔离了,不会相互改,并且在各自的进程中又有GIL锁,又保证各个线程数据的安全)。
本人在这里产生了一个问题,就是在一个线程中,当我们定义一个变量,比如a=1时候,我们知道,python定义一个变量,是先产生一个a放在栈区,然后在堆区产生数据1,然后把对应关系建立,此时a的引用计数才为1,那么如果当我们在a刚产生的时候,线程的时间片正好到了,那么此时a的引用计数为0,这个线程的GIL锁释放,又正好被垃圾回收线程抢到了,按照垃圾回收的原理,a应该被回收,那么GIL锁不是就无法保护我们的数据安全了吗?
其实答案很简单,因为在python在,变量的定义与赋值是一个原子性操作,原子性操作是不会被中断的,他不会被cpu的线程调度打断,即如果他开始了,那么一直到他结束,cpu都不会让出他的执行。所以也就不会产生上述的问题,其实在python中,有大量的原子性操作,即使像字典和类成员赋值这样的操作也是原子性。