Python并发编程-线程
Python并发编程-线程
作者:尹正杰
版权声明:原创作品,谢绝转载!否则将追究法律责任。
一.进程和线程
1>.什么是进程
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
在早期面向进程设计的计算机结构中,进程是程序的基本执行实体。
在当代面向线程设计的计算机结构中,进程是线程的容器。
程序是指令、数据及其组织形式的描述,进程是程序的实体。
2>.进程和程序的关系
程序是源代码编译后的文件,而在这些文件存放在磁盘上。
当程序被操作系统加载到内存中,就是进程,进程中存放着指令和数据(资源),它也是线程的容器。
Linux进程有父进程,子进程,windows的进程是平等关系。
3>.什么是线程
线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。
线程是独立调度和分派的基本单位。线程可以为操作系统内核调度的内核线程,如Win32线程;由用户进程自行调度的用户线程,如Linux平台的POSIX Thread;或者由内核与用户进程,如Windows 7的线程,进行混合调度。
同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)。
一个进程可以有很多线程,每条线程并行执行不同的任务。
4>.进程,线程的理解
现代操作系统提出进程的概念,每一个进程都认为自己独占所有的计算机硬件资源。
进程就是独立的王国,进程间不可以随便的共享数据。
线程就是省份,同一个进程内的线程可以共享进程的资源,每一个线程拥有自己独立的堆栈信息。
为了方便我们队线程和进程的理解,我们来画2组图,方便我们对python中的线程和进程的理解,其他语言线程与进程的关系如下图:
python语言线程与进程的关系如下图:
如上图所示,线程进程介绍如下:
(1)计算机工作最小单元是线程;
(2)启动一个python可执行文件->应用程序->最少要有一个进程>最少要一有一个线程
(3)应用场景:
IO密集型:线程
计算密集型:进程
(4)GIL,全局解释器所。
功能:保证同一个进程中只有一个线程同时被调用。(以上三点是所有语言的共同之处,只是CPYTHON多了一个GIL规则而已。)
5>.线程的状态
就绪(ready):
线程能够运行,但在等待被调度。可能线程刚刚创建启动,或刚刚从阻塞中恢复,或者被其它线程抢占。
运行(running):
线程正在运行。
阻塞(blocked):
线程等待外部时间发生而无法运行,如I/O操作。
终止(terminated):
线程完成或退出或被取消。
6>.python中的线程和进程
运行程序会启动一个解释器进程,线程共享一个解释器进程。
Python的线程开发使用标准库thread。
进程靠线程执行代码,至少有一个主线程,其它线程是工作线程。
主线程是第一个启动的线程。
父线程:如果线程A中启动了一个线程B,A就是线程B的父线程。
子线程:B就是A的子线程。
python的线程没有优先级,没有线程组的概念,也不能被销毁,停止,挂起,那也就没有恢复,中断了。
二.Thread类
1>.Thread类的构造方法
group: 为将来实现ThreadGroup类时的扩展保留。 target: 是run()方法要调用的可调用对象。默认为“无”,表示不调用任何内容。即线程调用的对象,就是目标函数。 name: 是线程名。默认情况下,一个唯一的名称由“Thread-N”构成,其中N是一个小的十进制数。 args: 是目标调用函数的参数元组。默认为()。 kwargs: 是目标调用的关键字参数字典。默认为{}。
2>.线程启动
1 #!/usr/bin/env python 2 #_*_conding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie 5 6 import threading 7 import time 8 9 def worker(size): 10 count = 0 11 while True: 12 if count > size: 13 """ 14 Python没有提供线程退出的方法,线程在下面的情况时退出: 15 1>.线程函数中语句执行完毕 16 2>.线程函数中抛出未处理的异常 17 """ 18 # raise RuntimeError(count) #抛异常 19 # return #函数返回 20 break 21 time.sleep(1) 22 count += 1 23 print("I'm working") 24 print("Finished") 25 26 """ 27 通过threading.Thread创建一个线程对象: 28 使用target指定目标函数, 29 使用name指定线程名称, 30 使用args对目标函数进行传参,线程传参和函数传参没有什么区别,本质上就是函数传参。 31 但是线程没有启动,需要调用start方法。 32 """ 33 t = threading.Thread(target=worker,name="worker",args=(5,)) #创建一个线程对象 34 t.start() #启动线程 35 36 print("=== main end ===")
=== main end === I'm working I'm working I'm working I'm working I'm working I'm working Finished
3>.threading的属性和方法
1 #!/usr/bin/env python 2 #_*_conding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie 5 6 import threading 7 import time 8 9 def worker(size): 10 count = 0 11 while True: 12 if count > size: 13 break 14 time.sleep(1) 15 count += 1 16 print("I'm working") 17 print("Finished") 18 19 def show_read_info(): 20 """ 21 threading的属性和方法: 22 current_thread(): 23 返回当前线程对象。 24 main_thread(): 25 返回主线程对象。 26 active_count(): 27 当前处于alive状态的线程个数,返回的值还包括主线程哟。 28 enumerate(): 29 返回所有活着的线程列表,不包括已经终止的线程和未开始的线程,返回的值还包括主线程哟。 30 get_ident(): 31 返回当前线程的ID,非0的整数。 32 """ 33 print("current thread = {}\nmain thread = {}\nactive count = {}\nenumberate list ={}\nThread ID = {}".format( 34 threading.current_thread(), 35 threading.main_thread(), 36 threading.active_count(), 37 threading.enumerate(), 38 threading.get_ident() 39 )) 40 41 t = threading.Thread(target=worker,name="worker",args=(5,)) #创建一个线程对象 42 43 show_read_info() 44 time.sleep(1) 45 t.start() #启动线程 46 show_read_info() 47 print("=== main end ===")
current thread = <_MainThread(MainThread, started 16776)> main thread = <_MainThread(MainThread, started 16776)> active count = 1 enumberate list =[<_MainThread(MainThread, started 16776)>] Thread ID = 16776 current thread = <_MainThread(MainThread, started 16776)> main thread = <_MainThread(MainThread, started 16776)> active count = 2 enumberate list =[<_MainThread(MainThread, started 16776)>, <Thread(worker, started 4048)>] Thread ID = 16776 === main end === I'm working I'm working I'm working I'm working I'm working I'm working Finished
4>.Thread实例的属性和方法
1 #!/usr/bin/env python 2 #_*_conding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie 5 6 import threading 7 import time 8 9 def worker(size): 10 count = 0 11 while True: 12 if count > size: 13 break 14 time.sleep(1) 15 count += 1 16 print("I'm working") 17 print("Finished") 18 19 class MyThread(threading.Thread): 20 def start(self): 21 time.sleep(1) 22 print("{0} start {0}".format("*" * 15)) 23 super().start() 24 25 def run(self): 26 time.sleep(1) 27 print("{0} run {0}".format("*" * 15)) 28 29 t = MyThread(target=worker,name="worker",args=(5,)) #创建一个线程对象 30 """ 31 Thread实例的属性和方法 32 name: 33 只是一个名字,只是个标识,名称可以重名。通过getName(),setName()可以获取,设置这个名字。 34 ident: 35 线程ID必须唯一,它是一个非0整数,线程启动后才会有ID,否则为None。线程退出,此ID依旧可以访问,此ID可以重复使用。 36 is_alive(): 37 返回线程是否活着。 38 start(): 39 启动一个新的线程并调用run方法。start方法只能调用1次(设置_startd属性实现) 40 run(): 41 并不启动新的线程,就是在主线程调用了一个普通函数而已。run方法可以多次调用。 42 """ 43 44 print("{} {} {}".format(t.name, t.ident, "alive" if t.is_alive() else "dead")) 45 print(threading.enumerate()) 46 t.start() 47 # t.run() 48 # t.run() 49 # t.run() 50 print("{} {} {}".format(t.name, t.ident, "alive" if t.is_alive() else "dead")) 51 print(threading.enumerate()) 52 time.sleep(3) 53 54 print("{} {} {}".format(t.name, t.ident, "alive" if t.is_alive() else "dead")) 55 print(threading.enumerate())
worker None dead [<_MainThread(MainThread, started 15888)>] *************** start *************** worker 16460 alive [<_MainThread(MainThread, started 15888)>, <MyThread(worker, started 16460)>] *************** run *************** worker 16460 dead [<_MainThread(MainThread, started 15888)>] Process finished with exit code 0
三.线程安全
1>.什么是线程安全
线程执行一段代码,不会产生不确定的结果,那这段代码就是线程安全的。
2>.打印线程不安全案例
1 #!/usr/bin/env python 2 #_*_conding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie 5 6 import threading 7 8 def worker(): 9 for i in range(100): 10 print("{} is running.".format(threading.current_thread().name)) #print函数不是线程安全函数。 11 12 13 for x in range(1,30): #可以增加线程数,使用pycharm + python3.7.4版本观察输出结果,发现print函数被打断了,被线程切换打断了,在这说明print函数是线程不安全的。 14 name = "worker {}".format(x) 15 t = threading.Thread(name=name,target=worker) 16 t.start()
3>.解决案例中打印线程不安全方案一(不让print打印换行)
#!/usr/bin/env python #_*_conding:utf-8_*_ #@author :yinzhengjie #blog:http://www.cnblogs.com/yinzhengjie import threading def worker(): for i in range(100): print("{} is running.\n".format(threading.current_thread().name),end="") #字符串是不可变类型,它可以作为一个整体不可分割输出。end=""就不在让print输出换行了。 for x in range(1,30): name = "worker {}".format(x) t = threading.Thread(name=name,target=worker) t.start()
4>.解决案例中打印线程不安全方案二(使用logging模块)
1 #!/usr/bin/env python 2 #_*_conding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie 5 6 import threading 7 import logging 8 9 def worker(): 10 for i in range(100): 11 logging.warning("{} is running.".format(threading.current_thread().name)) 12 13 14 for x in range(1,30): 15 name = "worker {}".format(x) 16 t = threading.Thread(name=name,target=worker) 17 t.start()
四.daemon线程和non-daemon线程
1>.demon与non-daemon线程案例
1 #!/usr/bin/env python 2 #_*_conding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie 5 6 import threading 7 import logging 8 import time 9 10 def worker(timeout): 11 time.sleep(timeout) 12 for i in range(10): 13 logging.warning("{} is running.".format(threading.current_thread().name)) 14 15 16 """ 17 daemon线程: 18 daemon属性:表示线程是否时daemon线程,这个值必须在start()之前设置,否则引发RuntimeError异常。 19 isDaemon属性:是否是daemon线程。 20 setDaemon设置威威daemon线程,必须咋子start方法之前设置。 21 22 总结: 23 线程具有一个daemon属性,可以手动设置为True或False,也可以不设置,则取默认值None。 24 如果不设置daemon,就取当前线程的daemon来设置它。 25 主线程是non-daemon线程,即daemon=False。 26 从主线程创建的所有线程的不设置daemon属性,则默认都是daemon=Flase,也就是non-daemon线程。 27 Python程序在没有或者的non-deamon线程运行时,程序退出,也就是除主线程之外剩下的只能都是daemon线程,主线程才能退出,否则主线程就只能等待。 28 """ 29 t1 = threading.Thread(target=worker,name="t1",args=(10,),daemon=True) 30 t2 = threading.Thread(target=worker,name="t2",args=(5,),daemon=False) 31 t1.start() 32 t2.start() 33 34 35 36 print("Main Thread Exits!")
Main Thread Exits! WARNING:root:t2 is running. WARNING:root:t2 is running. WARNING:root:t2 is running. WARNING:root:t2 is running. WARNING:root:t2 is running. WARNING:root:t2 is running. WARNING:root:t2 is running. WARNING:root:t2 is running. WARNING:root:t2 is running. WARNING:root:t2 is running.
2>.join方法和demon线程
1 #!/usr/bin/env python 2 #_*_conding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie 5 6 import threading 7 import logging 8 import time 9 10 def worker(): 11 for i in range(10): 12 time.sleep(1) 13 logging.warning("{} is running.".format(threading.current_thread().name)) 14 15 16 17 t1 = threading.Thread(target=worker,name="t1",daemon=True) 18 t1.start() 19 20 """ 21 join方法是线程的标准方法之一,相关总结如下: 22 一个线程中调用另一个线程的join方法,调用者将被阻塞,直到被调用者用线程终止 23 一个线程可以被join多次 24 join(timeout=None),timeout参数指定调用者等待多久,没有设置超时,就一直等到被调用线程结束 25 调用谁的join方法,就是join谁,就要等谁。 26 """ 27 # t1.join() 28 t1.join(5) 29 30 31 print("Main Thread Exits!")
WARNING:root:t1 is running. WARNING:root:t1 is running. WARNING:root:t1 is running. WARNING:root:t1 is running. Main Thread Exits!
3>.daemon线程应用场景
主要应用场景有: (1)后台任务 如发送心跳包,监控,这种场景最多。 (2)主线程工作才有用的线程
如主线程中维护着公共的资源,主线程已经清理了,准备退出,而工作线程使用这些资源工作也没有意义了,一起退出最合适。 (3)随时可以被终止的线程
如果主线程退出,想所有其它工作线程一起退出,就使用daemon=True来创建线程。比如,开启一个线程定时判断WEB服务是否正常工作,主线程退出,工作线程也没有必要存在了,应该随着主线程退出一起退出。这种daemon线程一旦创建,就可以忘记它了,只用关心主线程什么时候退出就行了。
五.threading.local类
1 #!/usr/bin/env python 2 #_*_conding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie 5 6 import threading 7 import logging 8 import time 9 10 """ 11 threading.local本质: 12 运行时,threading.local实例处在不同的线程中,就从大字典中找到当前线程相关键值对中的字典,覆盖threading.local实例的"__dict__"。 13 这样就可以在不同的线程中,安全地使用线程独有的数据,做到了线程间数据隔离,如同本地变量一样安全。 14 """ 15 global_data = threading.local() 16 17 def worker(): 18 global_data.x = 0 19 for i in range(1,10000): 20 time.sleep(0.0000001) 21 global_data.x += 1 22 print(threading.current_thread(),global_data.x) 23 24 25 for i in range(10): 26 threading.Thread(target=worker).start() 27 28 print("Main Thread Exits!")
Main Thread Exits!
<Thread(Thread-1, started 1080)> 9999
<Thread(Thread-7, started 16592)> 9999
<Thread(Thread-10, started 10780)> 9999
<Thread(Thread-2, started 4352)> 9999
<Thread(Thread-4, started 14460)> 9999
<Thread(Thread-3, started 11792)> 9999
<Thread(Thread-9, started 1620)> 9999
<Thread(Thread-8, started 14884)> 9999
<Thread(Thread-5, started 17152)> 9999
<Thread(Thread-6, started 5924)> 9999
本文来自博客园,作者:尹正杰,转载请注明原文链接:https://www.cnblogs.com/yinzhengjie/p/11875147.html,个人微信: "JasonYin2020"(添加时请备注来源及意图备注,有偿付费)
当你的才华还撑不起你的野心的时候,你就应该静下心来学习。当你的能力还驾驭不了你的目标的时候,你就应该沉下心来历练。问问自己,想要怎样的人生。