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 = {}
main thread = {}
active count = {}
enumberate list ={}
Thread 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.
".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