No.012-Python-学习之路-Day9-GIL|Thread|Process|Coroutine

GIL-Python全局解释器锁<global interpreter lock>

GIL官方说明:

In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)

GIL本质是一把互斥锁,是互斥锁其作用就是将并行变为串行,其意思在于Python的线程是调用os的原生线程,Python无法有效的控制;当一个进程内多个线程<包括解释器自己的线程>要操作一个数据block时,会造成数据异常;而最简单的办法就是加锁,所以在cpython内存在一个全局的锁即GIL,用于确保在同一个时间段内进程中只有一个线程是正常运行的。

GIL并非是Python的一个特性,这个是CPython中的一个设计缺陷,其造成CPython的线程无法利用多核运行;其在PyPy及JsPython中没有这个问题。

So Python的Thread适合处理IO密集型的任务,在处理计算密集型任务时,反而会因为频繁的context切换而造成运行速度变慢。

 GIL与普通互斥锁

GIL的存在保证了同一时刻只有一个线程在使用CPU,GIL在ticks=100IO操作的时候释放;

普通互斥锁则是为了在多线程时,保证修改共享数据时有序的修改,不会产生数据修改混乱;

(1)多线程运行,假设Thread1获得GIL可以使用cpu,这时Thread1获得 互斥锁lock,Thread1可以改date数据(但并
没有开始修改数据)

(2)Thread1线程在修改date数据前发生了 i/o操作 或者 ticks计数满100 (注意就是没有运行到修改data数据),这个
时候 Thread1 让出了Gil,Gil锁可以被竞争

(3) Thread1 和 Thread2 开始竞争 Gil (注意:如果Thread1是因为 i/o 阻塞 让出的Gil Thread2必定拿到Gil,如果
Thread1是因为ticks计数满100让出Gil 这个时候 Thread1 和 Thread2 公平竞争)

(4)假设 Thread2正好获得了GIL, 运行代码去修改共享数据date,由于Thread1有互斥锁lock,所以Thread2无法更改共享数据
date,这时Thread2让出Gil锁 , GIL锁再次发生竞争 

(5)假设Thread1又抢到GIL,由于其有互斥锁Lock所以其可以继续修改共享数据data,当Thread1修改完数据释放互斥锁lock,
Thread2在获得GIL与lock后才可对data进行修改。
一个例子

Thread与Process

线程<Thread>,是cpu能够调用的最小单位,是os发向cpu的一对指令,是进程中的实际运作单位;

进程<Process>, 每一个程序的内存是独立的,每个程序以整体的形式由os进行管理,os对程序进行整体的包装,里面包含各种资源的调用,如内存的管理,网络接口调用等;
对各种资源管理的集合就可以称为进程<Process>;

进程与线程的关系

进程要操作CPU,要执行,必须至少要先创建一个线程;进程本身并不具备执行能力,只是各种资源的集合;

在一个进程中的所有线程共享这个进程的内存空间,访问同一块数据时,为了保证有序性,使用互斥锁,串行访问;

每个进程启动时会创建一个线程即主线程,主线程可以创建子线程,但是子线程是独立的,除非被设置为守护线程,否则不受主线程影响;

进程与线程的区别:   

a.线程共享内存空间,进程<包括子进程>的内存是独立的,子进程copy父进程的内存并独立划分出自己的内存空间;   

b.同一个进程的线程们可以直接交流<涉及数据共享及信息传递>,两个进程间的通信,必须通过一个中间代理来实现;

c.新线程容易创建,新进程需要对其父进程进行一次克隆;

d.一个线程可以控制和操作同一进程里的其他线程,但是进程只能操作子进程;

e.对主线程的修改有可能影响到同一进程内的其他线程的行为,但是对一个父进程的修改,对子进程没有任何影响;

Thread的两种实现

直接调用

import threading, time
"""
the simplest threading;
"""

# 这是一个任务
def run(n):
print("task", n)
time.sleep(2)

# 启动两个线程执行这个任务
t1 = threading.Thread(target=run, args=("t1",))
t2 = threading.Thread(target=run, args=("t2",))
t1.start()
t2.start()

继承式调用

import threading, time
"""
the threading of a class;
"""

class Mythread(threading.Thread):

def __init__(self, n):
super(Mythread, self).__init__()
self.n = n

def run(self):
print("running task", self.n)
time.sleep(10)


t1 = Mythread("t1")
t2 = Mythread("t2")
t1.start()
t2.start()

Thread的deamon线程

Some threads do background tasks, like sending keepalive packets, or performing periodic garbage collection, or whatever. These are only useful when the main program is running, and it's okay to kill them off once the other, non-daemon, threads have exited.

Without daemon threads, you'd have to keep track of them, and tell them to exit, before your program can completely quit. By setting them as daemon threads, you can let them run and forget about them, and when your program quits, any daemon threads are killed automatically.

 Note:Daemon threads are abruptly stopped at shutdown. Their resources (such as open files, database transactions, etc.) may not be released properly. If you want your threads to stop gracefully, make them non-daemonic and use a suitable signalling mechanism such as an Event.

 

end

参考文档:

GIL相关:  https://www.cnblogs.com/value-code/p/8572852.html

      https://blog.csdn.net/weixin_41594007/article/details/79485847

总文档参考:https://www.cnblogs.com/alex3714/articles/5230609.html

猜你喜欢

转载自www.cnblogs.com/FcBlogPythonLinux/p/12542732.html