python多线程

Posted it-scavenger

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python多线程相关的知识,希望对你有一定的参考价值。

开启线程的两种方式

#方式一
from threading import Thread
import time
def sayhi(name):
   time.sleep(2)
   print(‘%s say hello‘ %name)

if __name__ == ‘__main__‘:
   t=Thread(target=sayhi,args=(‘egon‘,))
   t.start()
   print(‘主线程‘)

 

#方式二
from threading import Thread
import time
class Sayhi(Thread):
   def __init__(self,name):
       super().__init__()
       self.name=name
   def run(self):
       time.sleep(2)
       print(‘%s say hello‘ % self.name)

if __name__ == ‘__main__‘:
   t = Sayhi(‘egon‘)
   t.start()
   print(‘主线程‘)

 

技术分享图片

 

在一个进程下开启多个线程与在一个进程下开启多个子进程的区别

谁的开启速度快

from threading import Thread
from multiprocessing import Process
import os
def work():
   print(‘hello‘)
if __name__ == ‘__main__‘:
   #在主进程下开启线程
   t=Thread(target=work)
   t.start()
   print(‘主线程/主进程‘)
   ‘‘‘
   打印结果:
   hello
   主线程/主进程
   ‘‘‘

   #在主进程下开启子进程
   t=Process(target=work)
   t.start()
   print(‘主线程/主进程‘)
   ‘‘‘
   打印结果:
   主线程/主进程
   hello
   ‘‘‘

 

瞅一瞅pid

from threading import Thread
from multiprocessing import Process
import os

def work():
   print(‘hello‘,os.getpid())
if __name__ == ‘__main__‘:
   #part1:在主进程下开启多个线程,每个线程都跟主进程的pid一样
   t1=Thread(target=work)
   t2=Thread(target=work)
   t1.start()
   t2.start()
   print(‘主线程/主进程pid‘,os.getpid())
   #part2:开多个进程,每个进程都有不同的pid
   p1=Process(target=work)
   p2=Process(target=work)
   p1.start()
   p2.start()
   print(‘主线程/主进程pid‘,os.getpid())

 

同一进程内的线程共享该进程的数据?

from  threading import Thread
from multiprocessing import Process
import os
def work():
   global n
   n=0
if __name__ == ‘__main__‘:
   # n=100
   # p=Process(target=work)
   # p.start()
   # p.join()
   # print(‘主‘,n) #毫无疑问子进程p已经将自己的全局的n改成了0,但改的仅仅是它自己的,查看父进程的n仍然为100
   n=1
   t=Thread(target=work)
   t.start()
   t.join()
   print(‘主‘,n) #查看结果为0,因为同一进程内的线程之间共享进程内的数据

 

线程相关的其他方法

Thread实例对象的方法
 # isAlive(): 返回线程是否活动的。
 # getName(): 返回线程名。
 # setName(): 设置线程名。
threading模块提供的一些方法:
 # threading.currentThread(): 返回当前的线程变量。
 # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
 # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
复制代码

 

主线程等待子线程结束

from threading import Thread
import time
def sayhi(name):
   time.sleep(2)
   print(‘%s say hello‘ %name)

if __name__ == ‘__main__‘:
   t=Thread(target=sayhi,args=(‘egon‘,))
   t.start()
   t.join()
   print(‘主线程‘)
   print(t.is_alive())
   ‘‘‘
   egon say hello
   主线程
   False
   ‘‘‘

 

守护线程 

无论是进程还是线程,都遵循:守护xxx会等待主xxx运行完毕后被销毁

需要强调的是:运行完毕并非终止运行

#1.对主进程来说,运行完毕指的是主进程代码运行完毕

#2.对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,
主线程才算运行完毕

 

详细解释:

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

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

 

from threading import Thread
import time
def sayhi(name):
   time.sleep(2)
   print(‘%s say hello‘ %name)
if __name__ == ‘__main__‘:
   t=Thread(target=sayhi,args=(‘egon‘,))
   t.setDaemon(True) #必须在t.start()之前设置
   t.start()
   print(‘主线程‘)
   print(t.is_alive())
   ‘‘‘
   主线程
   True
   ‘‘‘

 

Python GIL(Global Interpreter Lock)

在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势

首先需要明确的一点是GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。就好比C++是一套语言(语法)标准,但是可以用不同的编译器来编译成可执行代码。有名的编译器例如GCC,INTEL C++,Visual C++等。Python也一样,同样一段代码可以通过CPython,PyPy,Psyco等不同的Python执行环境来执行。像其中的JPython就没有GIL。然而因为CPython是大部分环境下默认的Python执行环境。所以在很多人的概念里CPython就是Python,也就想当然的把GIL归结为Python语言的缺陷。所以这里要先明确一点:GIL并不是Python的特性,Python完全可以不依赖于GIL

 

GIL本质就是一把互斥锁,既然是互斥锁,所有互斥锁的本质都一样,都是将并发运行变成串行,以此来控制同一时间内共享数据只能被一个任务所修改,进而保证数据安全。

可以肯定的一点是:保护不同的数据的安全,就应该加不同的锁。

要想了解GIL,首先确定一点:每次执行python程序,都会产生一个独立的进程。例如python test.py,python aaa.py,python bbb.py会产生3个不同的python进程

在一个python的进程内,不仅有test.py的主线程或者由该主线程开启的其他线程,还有解释器开启的垃圾回收等解释器级别的线程,总之,所有线程都运行在这一个进程内,毫无疑问

如果多个线程的target=work,那么执行流程是

多个线程先访问到解释器的代码,即拿到执行权限,然后将target的代码交给解释器的代码去执行

解释器的代码是所有线程共享的,所以垃圾回收线程也可能访问到解释器的代码而去执行,这就导致了一个问题:对于同一个数据100,可能线程1执行x=100的同时,而垃圾回收执行的是回收100的操作,解决这种问题没有什么高明的方法,就是加锁处理,如下图的GIL,保证python解释器同一时间只能执行一个任务的代码

技术分享图片

GIL与Lock

GIL保护的是解释器级的数据,保护用户自己的数据则需要自己加锁处理,如下图

技术分享图片

GIL与多线程

有了GIL的存在,同一时刻同一进程中只有一个线程被执行

听到这里,有的朋友立马质问:进程可以利用多核,但是开销大,而python的多线程开销小,但却无法利用多核优势,也就是说python没用了,php才是最牛逼的语言?

别着急啊,还没讲完呢。

要解决这个问题,我们需要在几个点上达成一致:

#1. cpu到底是用来做计算的,还是用来做I/O的?
#2. 多cpu,意味着可以有多个核并行完成计算,所以多核提升的是计算性能
#3. 每个cpu一旦遇到I/O阻塞,仍然需要等待,所以多核对I/O操作没什么用处

 

一个工人相当于cpu,此时计算相当于工人在干活,I/O阻塞相当于为工人干活提供所需原材料的过程,工人干活的过程中如果没有原材料了,则工人干活的过程需要停止,直到等待原材料的到来。

如果你的工厂干的大多数任务都要有准备原材料的过程(I/O密集型),那么你有再多的工人,意义也不大,还不如一个人,在等材料的过程中让工人去干别的活,

反过来讲,如果你的工厂原材料都齐全,那当然是工人越多,效率越高

结论:

  对计算来说,cpu越多越好,但是对于I/O来说,再多的cpu也没用

  当然对运行一个程序来说,随着cpu的增多执行效率肯定会有所提高(不管提高幅度多大,总会有所提高),这是因为一个程序基本上不会是纯计算或者纯I/O,所以我们只能相对的去看一个程序到底是计算密集型还是I/O密集型,从而进一步分析python的多线程到底有无用武之地

#分析:
我们有四个任务需要处理,处理方式肯定是要玩出并发的效果,解决方案可以是:
方案一:开启四个进程
方案二:一个进程下,开启四个线程

#单核情况下,分析结果: 
  如果四个任务是计算密集型,没有多核来并行计算,方案一徒增了创建进程的开销,方案二胜
  如果四个任务是I/O密集型,方案一创建进程的开销大,且进程的切换速度远不如线程,方案二胜

#多核情况下,分析结果:
  如果四个任务是计算密集型,多核意味着并行计算,在python中一个进程中同一时刻只有一个
  线程执行用不上多核,方案一胜
  如果四个任务是I/O密集型,再多的核也解决不了I/O问题,方案二胜
#结论:现在的计算机基本上都是多核,python对于计算密集型的任务开多线程的效率并不能带来多大性
能上的提升,甚至不如串行(没有大量切换),但是,对于IO密集型的任务效率还是有显著提升的。

 

多线程性能测试

计算密集型:多进程效率高

from multiprocessing import Process
from threading import Thread
import os,time
def work():
   res=0
   for i in range(100000000):
       res*=i


if __name__ == ‘__main__‘:
   l=[]
   print(os.cpu_count()) #本机为4核
   start=time.time()
   for i in range(4):
       p=Process(target=work) #耗时5s多
       p=Thread(target=work) #耗时18s多
       l.append(p)
       p.start()
   for p in l:
       p.join()
   stop=time.time()
   print(‘run time is %s‘ %(stop-start))

 

I/O密集型:多线程效率高

from multiprocessing import Process
from threading import Thread
import threading
import os,time
def work():
   time.sleep(2)
   print(‘===>‘)

if __name__ == ‘__main__‘:
   l=[]
   print(os.cpu_count()) #本机为4核
   start=time.time()
   for i in range(400):
       # p=Process(target=work) #耗时12s多,大部分时间耗费在创建进程上
       p=Thread(target=work) #耗时2s多
       l.append(p)
       p.start()
   for p in l:
       p.join()
   stop=time.time()
   print(‘run time is %s‘ %(stop-start))

 

应用:

多线程用于IO密集型,如socket,爬虫,web
多进程用于计算密集型,如金融分析

 


 

技术分享图片

识别图中二维码,欢迎关注python宝典



























































































































































































以上是关于python多线程的主要内容,如果未能解决你的问题,请参考以下文章

[Python3] 043 多线程 简介

python中的多线程和多进程编程

多线程 Thread 线程同步 synchronized

多个用户访问同一段代码

在 Python 多处理进程中运行较慢的 OpenCV 代码片段

线程学习知识点总结