对码当歌,猿生几何?

【Python与线程】--2019-08-06 15:23:10

        nblogs-markdown">

原创链接: http://106.13.73.98/__/6/

 

目录

一、全局解释器锁GIL

二、Python线程模块的选择

三、线程的创建

三、锁机制

四、信号量

五、事件

六、条件

七、定时器

八、线程队列

九、线程池


补充:线程安全

import threadingobj = threading.local()# local():可实现,多线程操作某一数据,不会出现数据混乱的情况# 原理:空间换时间def add(i):obj.n = iprint(i, obj.n, threading.current_thread().ident)for i in range(20):th = threading.Thread(target=add, args=(i,))th.start()

 


一、全局解释器锁GIL

Python代码的执行由Python虚拟机(也叫解释器主循环)来控制。Python在设计之初就考虑到要在主循环中同时只有一个线程在执行。虽然Python解释器中可以“运行”多个线程,但在任意时刻只有一个线程在解释器中运行.

对于Python虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁保证了同一时刻只有一个线程在运行.

同一时间点,GIL只允许同一个进程中的一个线程访问cpu,即CPython解释器中没有真正的线程并行,只有进程可以实现。故I/O操作多时,使用多线程最好;计算密集时,使用多进程最好。

  • 在多线程环境中,Python虚拟机按以下方式执行:

1. 设置GIL;

2. 切换到一个线程去执行;

3. 运行指定数量的字节码指令或者线程主动让出控制(如调用time.sleep(0));

4. 把线程设置为睡眠状态;

5. 解锁GIL;

6. 再次重复以上所有步骤;

在调用外部代码(如C/C++扩展函数)的时候,GIL将会被锁定,知道这个函数结束为止(由于在这期间没有Python的字节码被运行,所以不会做线程切换),编写扩展的程序员可以主动解锁GIL。


二、Python线程模块的选择

1. Python提供了多个用于多线程编程的模块,包括thread、threading和Queue等。thread和threading模块允许程序员创建和管理线程。thread模块提供了基本的线程和锁的支持;threading提供了更高级别、功能更强的线程管理功能。而Queue模块允许用户创建一个可以用于多个线程之间共享数据的队列数据结构。

2. 应避免使用thread模块,因为更高级别的threading模块更为先进,对线程的支持更加完善。而且thread模块里的属性有可能会与threading出现冲突;其次,低级别的thread模块的同步原理很少(实际上只有一个),而threading模块则有很多;再则,thread模块中当主线程结束时,所有的子线程都会被强制结束掉,没有警告也不会做正常的清除工作,但threading模块至少能够确保重要的子线程退出后才退出主线程.

3.关于守护线程:thread模块不支持守护线程,当主线程退出时,所有的子线程不论它们是否还在工作,都会被强制退出。而threading模块支持守护线程,守护线程一般是一个等待客户请求的服务器,若没有客户提出请求它就在那等着,如果设定这个线程为守护线程,就表示这个线程是不重要的,在进程(主线程)退出的时候不用等待这个线程退出.


三、线程的创建

1. 创建线程

# 创建方式1from threading import Threadfrom time import sleepdef sayhi(name):sleep(1)print(name, 'say hello')t = Thread(target=sayhi, args=('egon',))t.start()print("主线程")
# 创建方式2:自定义线程类from threading import Threadfrom time import sleepclass Sayhi(Thread):def __init__(self, name):super().__init__()self.name = namedef run(self):    # 必备方法,用于启动线程sleep(1)print(self.name, 'say hello')t = Sayhi('egon')t.start()print("主线程")

2. 多线程与多进程

  • pid比较

from threading import Threadfrom multiprocessing import Processfrom os import getpidwork = lambda who:print(who, getpid())if __name__ == '__main__':# part1: 在主进程下开启多个线程,每个线程都与主进程的pid一样t1 = Thread(target=work, args=("线程",))t2 = Thread(target=work, args=("线程",))for t in (t1, t2):t.start()t.join()# part2: 开启多个子进程,每个进程都有不同的pidp1 = Process(target=work, args=("进程",))p2 = Process(target=work, args=("进程",))for p in (p1, p2):p.start()p.join()print("主线程/主进程", getpid())
  • 启动速度的较量

# 单线程与单进程from threading import Threadfrom multiprocessing import Processwork = lambda who:print("我是%s" % who)if __name__ == '__main__':p = Process(target=work, args=("进程",))p.start()t = Thread(target=work, args=("线程",))t.start()# 结果必然是线程先打印出来
from threading import Threadfrom multiprocessing import Processfrom time import time, sleepdef func():passdef work(Process, Thread):# 注意:这是测试启动速度,所以无需join# 进程p_start = time()[Process(target=func).start() for i in range(100)]p_over = time() - p_start# 线程t_start = time()[Thread(target=func).start() for i in range(100)]t_over = time() - t_startprint("进程用时:%s		线程用时:%s" % (p_over, t_over))# 进程用时:0.21951699256896973 线程用时:0.015702009201049805# 经过多次测试后得到结论:多线程的启动速度远快于多进程的启动速度if __name__ == '__main__':Process(target=work, args=(Process, Thread)).start()

3. 线程与进程内存数据共享的区别

from threading import Threadfrom multiprocessing import Processdef work():global nn = 0if __name__ == '__main__':n = 1p = Process(target=work)p.start()p.join()print("父进程:", n)# 显然,子进程中改的仅仅是它自己的全局n,不会影响到父进程t = Thread(target=work)t.start()t.join()print("主线程:", n)# 此时查看结果为0,因为同一进程内的线程共享进程内的数据

4. 守护线程

无论是线程还是进程,都遵循守护线程(进程)等待主线程(进程)运行完毕后被销毁。需要强调的是,运行完毕并非终止运行:对于主进程来说,运行完毕指的是主进程代码运行完毕;对于主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕.

  • 详细说明

1. 主进程在其代码执行结束后就已经算运行完毕了(守护进程在此时就被回收),然后主进程会一直等非守护的子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程),才会结束。

2. 主线程在其它非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收),因为主线程的结束意味着进程的结束,进程整体的资源都将被回收,而进程必须保证非守护线程都运行完毕后才能结束。

  • 实例

# 守护线程与守护进程的区别from threading import Threadfrom multiprocessing import Processfrom time import sleepdef func</span&