1、python多线程

使用一个例子来学习多线程。建议自己敲一遍。
python多线程是通过threading模块的Thread实现。
创建线程对象 t = thread.Thread()
启动线程 t.start()

import threading
import timedef say(name):print("你好%s at %s"%(name, time.ctime()))time.sleep(2)print("结束%s at %s"%(name, time.ctime()))def listen(name):print("你好%s at %s"%(name, time.ctime()))time.sleep(4)print("结束%s at %s"%(name, time.ctime()))if __name__=='__main__':t1 = threading.Thread(target=say, args = ('tony',))t1.start()t2 = threading.Thread(target = listen, args = ('simon',))t2.start()print("程序结束=============")
你好tony at Wed Mar 24 17:58:32 2021
你好simon at Wed Mar 24 17:58:32 2021
程序结束=============
结束tony at Wed Mar 24 17:58:34 2021
结束simon at Wed Mar 24 17:58:36 2021

可以看出主线程不是在两个子线程运行结束后退出的。这是因为主线程和两个子线程是同时跑的,但是子线程跑完后,主线程才会退出。

但是有时候我们想要子线程跑完后,再继续跑主线程,这个时候就引入了join

import threading
import timedef say(name):print("你好%s at %s"%(name, time.ctime()))time.sleep(2)print("结束%s at %s"%(name, time.ctime()))def listen(name):print("你好%s at %s"%(name, time.ctime()))time.sleep(4)print("结束%s at %s"%(name, time.ctime()))if __name__=='__main__':t1 = threading.Thread(target=say, args = ('tony',))t1.start()t2 = threading.Thread(target = listen, args = ('simon',))t2.start()t1.join()t2.join()print("程序结束=============")
你好tony at Wed Mar 24 18:01:51 2021
你好simon at Wed Mar 24 18:01:51 2021
结束tony at Wed Mar 24 18:01:53 2021
结束simon at Wed Mar 24 18:01:55 2021
程序结束=============

主线程最后执行print操作,并退出。但是如果不加join,主线程执行打印,但是主线程还没有结束,还需要等待子线程结束后,主线程才结束。

上面的子线程是非守护子线程,默认的子线程都是主线程的非守护子线程。有时候我们需要当主线程结束,不管主线程有没有结束,子线程都要跟随主线程一起退出,这个时候就需要守护线程

如果某个线程是守护线程,那么主线程就不需要等待这个子线程,当其他非守护线程运行结束后,主线程就退出。
下面我们看个例子,设置t2为守护进程。

import threading
import timedef say(name):print("你好%s at %s"%(name, time.ctime()))time.sleep(2)print("结束%s at %s"%(name, time.ctime()))def listen(name):print("你好%s at %s"%(name, time.ctime()))time.sleep(4)print("结束%s at %s"%(name, time.ctime()))if __name__=='__main__':t1 = threading.Thread(target=say, args = ('tony',))t1.start()t2 = threading.Thread(target = listen, args = ('simon',))t2.setDaemon(True)t2.start()print("程序结束=============")
你好tony at Wed Mar 24 18:07:47 2021
你好simon at Wed Mar 24 18:07:47 2021
程序结束=============
结束tony at Wed Mar 24 18:07:49 2021

t2进程是sleep 4s,主线程不等待t2进程结束,就退出了。
thread的一些方法:

  • join():在子线程完成运行之前,这个子线程的父线程将一直被阻塞。
  • setDaemon(True) 在start()之前设置,这个方法和join是相仿的。
  • run():线程被cpu调度后自动执行线程对象的run方法
  • start() 启动线程活动
  • isAlibe() 返回线程是否活动的。

上面的进程,如果分开跑,需要6s,但是实际上用了4s,说明性能提升了,但是这种提升是cpu并发实现的提升,也就是cpu线程切换(多道技术)带来的,而不是cpu的并行执行。

并发:指一个系统具有处理多个任务的能力(cpu切换,多道技术)
并行:指一个系统有同时处理多个任务的能力(cpu同时处理)
并行是并发的一种情况,子集。

python 多线程不能实现真正的并行操作,是因为GIL(全局解释器锁

我们对任务进行分类:

  • IO密集型(各个线程都会各种的等待,如果有等待,切换线程是比较核是的),也可以采用多线程+协程
  • 计算密集型(线程在计算过程中没有等待,这时候没必要做切换)
    多线程学习传送门
    一篇文章搞懂Python多线程简单实现和GIL

2.多线程同步锁、死锁和递归锁

如果python多个线程要用到相同的数据,就会存在资源争用和锁的问题。

2.1 同步锁

通常用来实现对共享资源的同步访问。为每一个共享资源创建一个Lock对象。如果需要方位该资源,调用acquire方法来湖区对象(如果其他线程已经获取了该锁,则当前线程需要等待其被释放),使用release方法释放锁。
先看下面的案例

num = 100def fun_sub():global numnum2 = numtime.sleep(0.001)num = num2 - 1if __name__ == '__main__':print('开始测试同步锁 at %s' % time.ctime())thread_list = []for thread in range(100):t = threading.Thread(target = fun_sub)t.start()thread_list.append(t)for t in thread_list:t.join()print('num is %d'%num)print('结束测试同步锁 at %s'%time.ctime())
num is 72
结束测试同步锁 at Wed Mar 24 22:06:38 2021

上面的例子,我们每个线程从公共资源num变量执行减1操作,正常情况下,等到代码执行结束,得到的num为0。但是结果却是72,分析下代码执行流程。

  1. 因为GIL,只有一个线程(假设线程1)拿到了num资源,然后把变量赋值给num2,sleep 0.001秒,这时候num=100。
  2. 第一个线程sleep的时候,这个线程会做yield操作,就是cpu切换给别的线程(假设线程2拿到GIL,获得cpu使用权),线程2拿到和线程1一样的num,返回赋值给这时候num有可能还是100,然后sleep,这个时候num还是100.
  3. 线程2 sleep的时候,又要yield操作,假设线程3拿到num,执行上面的操作,num还有可能是100.
  4. 等到后面cpu重新切换给线程1,线程2,线程3执行时,他们执行减1操作后,其实得到的num都是99,不是顺序递减的。
  5. 其他线程操作如上

所以实际的运行过程并不是我们想象的按顺序减,这个时候就需要python的同步锁了,同一时间只能放一个线程来操作num变量,减1后,后面的线程操作来操作num。
使用同步锁,一次只有一个线程操作同享资源。

num = 100def fun_sub():global numlock.acquire()print('---------加锁------', t.name)num2 = numtime.sleep(0.001)num = num2 - 1lock.release()print('--------释放锁----------', num)if __name__ == '__main__':print('开始测试同步锁 at %s' % time.ctime())lock = threading.Lock() #创建锁thread_list = []for thread in range(100):t = threading.Thread(target = fun_sub)t.start()thread_list.append(t)print('join')for t in thread_list:t.join()print('num is %d'%num)print('结束测试同步锁 at %s'%time.ctime())
num is 0
结束测试同步锁 at Wed Mar 24 18:35:09 2021

2.2 python死锁

介绍下死锁怎么产生的:

  1. A拿了一个苹果
  2. B拿了一个香蕉
  3. A现在想再拿个香蕉,就在等待B释放这个香蕉
  4. B同时想要再拿个苹果,这时候就等待A释放苹果
  5. 这样就是陷入了僵局,这就是生活中的死锁

python中在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁,因为系统判断这部分资源都正在使用,所有这两个线程在无外力作用下将一直等待下去。下面是一个死锁的例子:

lock_apple = threading.Lock()
lock_banana = threading.Lock()class MyThread(threading.Thread):def __init__(self):threading.Thread.__init__(self)def run(self):self.fun1()self.fun2()def fun1(self):lock_apple.acquire()print("线程 %s, 想拿: %s--%s"%(self.name,"苹果",time.ctime()))lock_banana.acquire()print("线程%s, 想拿: %s--%s"%(self.name,"香蕉",time.ctime()))lock_banana.release()lock_apple.release()def fun2(self):lock_banana.acquire()print("线程%s, 想拿: %s--%s"%(self.name,"香蕉",time.ctime()))time.sleep(0.1)lock_apple.acquire()print("线程 %s, 想拿: %s--%s"%(self.name,"苹果",time.ctime()))lock_apple.release()lock_banana.release()if __name__ == "__main__":for i in range(0, 10):my_thread = MyThread()my_thread.start()
线程 Thread-1, 想拿: 苹果--Wed Mar 24 22:42:14 2021
线程Thread-1, 想拿: 香蕉--Wed Mar 24 22:42:14 2021
线程Thread-1, 想拿: 香蕉--Wed Mar 24 22:42:14 2021
线程 Thread-2, 想拿: 苹果--Wed Mar 24 22:42:14 2021

代码处理流程:

  1. fun1中,线程1先拿了苹果,然后拿了香蕉,然后释放香蕉和苹果,然后再在fun2中又拿了香蕉,sleep 0.1秒。
  2. 在线程1的执行过程中,线程2进入了,因为苹果被线程1释放了,线程2这时候获得了苹果,然后想拿香蕉
  3. 这时候就出现问题了,线程一拿完香蕉之后想拿苹果,返现苹果被线程2拿到了,线程2拿到苹果执行,想拿香蕉,发现香蕉被线程1持有了
  4. 双向等待,出现死锁,代码执行不下去了

2.3 Python递归锁RLock

import threading
import timelock = threading.RLock()  #递归锁class MyThread(threading.Thread):def __init__(self):threading.Thread.__init__(self)def run(self):self.fun1()self.fun2()def fun1(self):lock.acquire()  # 如果锁被占用,则阻塞在这里,等待锁的释放print ("线程 %s , 想拿: %s--%s" %(self.name, "苹果",time.ctime()))lock.acquire()print ("线程 %s , 想拿: %s--%s" %(self.name, "香蕉",time.ctime()))lock.release()lock.release()def fun2(self):lock.acquire()print ("线程 %s , 想拿: %s--%s" %(self.name, "香蕉",time.ctime()))time.sleep(0.1)lock.acquire()print ("线程 %s , 想拿: %s--%s" %(self.name, "苹果",time.ctime()))lock.release()lock.release()if __name__ == "__main__":for i in range(0, 10):  #建立10个线程my_thread = MyThread()  #类继承法是python多线程的另外一种实现方式my_thread.start()

上面我们用一把递归锁,就解决了多个同步锁导致的死锁问题。大家可以把RLock理解为大锁中还有小锁,只有等到内部所有的小锁,都没有了,其他的线程才能进入这个公共资源。

3.多进程

3.1 多进程模块multiprocessing

from multiprocessing import Process# sample
def fun1(name):print("测试%s多进程"%name)if __name__ == '__main__':process_list = []for i in range(5):p = Process(target = fun1, args=('python',))p.start()process_list.append(p)for i in process_list:p.join()print('结束测试')

使用类继承方法实现多进程

## inherit
class MyProcess(Process):def __init__(self, name):super(MyProcess, self).__init__()self.name = namedef run(self):print('测试%s多进程'%self.name)if __name__ =='__main__':process_list = []for i in range(5):p = MyProcess('Python')p.start()process_list.append(p)for i in process_list:p.join()print('测试结束')

3.2 python多线程通信

进程是系统独立调度核分配系统资源(CPU、内存)的基本单位,进程之间是相互独立的,每启动一个新的进程相当于把数据进行了一次克隆,子进程里的数据修改无法影响到主进程中的数据,不同子进程之间的数据也不能共享,这是多进程在使用中与多线程最明显的区别。但是难道Python多进程中间难道就是孤立的吗?当然不是,python也提供了多种方法实现了多进程中间的通信和数据共享(可以修改一份数据)。

3.2.1进程队列 Queue

通过Queue获取子进程中put的数据,实现进程间的通信。

from multiprocessing import Process, Queuedef fun1(q, i):print('子进程%s 开始put数据'%i)q.put('我是%s通过Queue通信'%i)if __name__ == '__main__':q = Queue()process_list = []for i in range(3):p = Process(target=fun1, args = (q, i))p.start()process_list.append(p)for i in process_list:p.join()print('主进程获取Queue数据')print(q.get())print(q.get())print(q.get())print('结束测试')

3.2.2 管道Pipe

管道Pipe和Queue的作用大致差不多,也是实现进程间的通信

from multiprocessing import Process, Pipe
def fun1(conn):print('子进程发送消息:')conn.send('你好主进程')print('子进程接收消息:')print(conn.recv())conn.close()if __name__ == '__main__':conn1, conn2 = Pipe()p = Process(target = fun1, args = (conn2, ))p.start()print('主进程接受消息:')print(conn1.recv())print('主进程发送消息: ')conn1.send("你好子进程")p.join()print('测试结束')
主进程接受消息:
子进程发送消息:
子进程接收消息:
你好主进程
主进程发送消息:
你好子进程
测试结束

3.2.3 Manager

Queue和Pipe只是实现了数据交互,并没实现数据共享,即一个进程去更改另一个进程的数据。那么就要用到Managers。

from multiprocessing import Process, Manager
def fun1(dic, lis, index):dic[index] = 'a'dic['2'] = 'b'lis.append(index)if __name__ == '__main__':with Manager() as manager:dic = manager.dict()l = manager.list(range(5))process_list = []for i in range(10):p = Process(target = fun1, args = (dic, l, i))p.start()process_list.append(p)for res in process_list:res.join()print(dic)print(l)
{0: 'a', '2': 'b', 1: 'a', 2: 'a', 3: 'a', 4: 'a', 5: 'a', 6: 'a', 9: 'a', 8: 'a', 7: 'a'}
[0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 5, 6, 9, 8, 7]

可以看到主进程定义了一个字典和一个列表,在子进程中,可以添加和修改字典的内容,在列表中插入新的数据,实现进程间的数据共享,即可以共同修改同一份数据.

3.2.4 进程池

进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进进程,那么程序就会等待,直到进程池中有可用进程为止。就是固定有几个进程可以使用。

3.2.4 进程池map方法

import os
import PIL from multiprocessing import Pool
from PIL import ImageSIZE = (75,75)
SAVE_DIRECTORY = \'thumbs\'def get_image_paths(folder):return (os.path.join(folder, f) for f in os.listdir(folder) if \'jpeg\' in f)def create_thumbnail(filename): im = Image.open(filename)im.thumbnail(SIZE, Image.ANTIALIAS)base, fname = os.path.split(filename) save_path = os.path.join(base, SAVE_DIRECTORY, fname)im.save(save_path)if __name__ == \'__main__\':folder = os.path.abspath(\'11_18_2013_R000_IQM_Big_Sur_Mon__e10d1958e7b766c3e840\')os.mkdir(os.path.join(folder, SAVE_DIRECTORY))images = get_image_paths(folder)pool = Pool()pool.map(creat_thumbnail, images) #关键点,images是一个可迭代对象pool.close()pool.join()

上边这段代码的主要工作就是将遍历传入的文件夹中的图片文件,一一生成缩略图,并将这些缩略图保存到特定文件夹中。这我的机器上,用这一程序处理 6000 张图片需要花费 27.9 秒。 map 函数并不支持手动线程管理,反而使得相关的 debug 工作也变得异常简单。

map在爬虫的领域里也可以使用,比如多个URL的内容爬取,可以把URL放入元祖里,然后传给执行函数。

python多进程、多线程相关推荐

  1. python多进程多线程,多个程序同时运行

    python 多线程 多进程同时运行 多任务要求 python 基础语法 python 文件目录操作 python 模块应用 开发工具 pycharm 实现方法 多任务的实现可以用进程和线程来实现 进 ...

  2. Python 多进程开发与多线程开发

    我们先来了解什么是进程? 程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程.程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本:进程 ...

  3. python 多进程_说说Python多线程与多进程的区别?

    公众号新增加了一个栏目,就是每天给大家解答一道Python常见的面试题,反正每天不贪多,一天一题,正好合适,只希望这个面试栏目,给那些正在准备面试的同学,提供一点点帮助! 小猿会从最基础的面试题开始, ...

  4. 一文看懂Python多进程与多线程编程(工作学习面试必读)

    进程(process)和线程(thread)是非常抽象的概念, 也是程序员必需掌握的核心知识.多进程和多线程编程对于代码的并发执行,提升代码效率和缩短运行时间至关重要.小编我今天就来尝试下用一文总结下 ...

  5. python 多进程和多线程

    python 多进程和多线程 一.进程和线程 1.概念 进程: 一个进程就是一个任务,可以理解为一个程序.一个进程可以有多个线程,至少一个.多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影 ...

  6. Python实现多进程/多线程同时下载单个文件

    功能描述: 使用多进程/多线程同时下载单个文件,可以自定义文件地址.进程/线程数量. 主要思路: 获取文件大小,使用多个进程/线程分别下载一部分,最后再把这些文件拼接起来. 参考代码: 运行结果: - ...

  7. python随笔系列--多进程多线程并发度初探

    大家都知道python中由于GIL这把大锁的存在,导致python的多线程并不是真正的多线程(不同于java等语言).首先了解下GIL导致的现象:同一时间只能有一个线程占有python解释器(或者,同 ...

  8. 关于python的多线程和多进程_Python的多线程和多进程

    (1)多线程的产生并不是因为发明了多核CPU甚至现在有多个CPU+多核的硬件,也不是因为多线程CPU运行效率比单线程高.单从CPU的运行效率上考虑,单任务进程及单线程效率是最高的,因为CPU没有任何进 ...

  9. 【干货】python多进程和多线程谁更快

    python多进程和多线程谁更快 python3.6 threading和multiprocessing 自从用多进程和多线程进行编程,一致没搞懂到底谁更快.网上很多都说python多进程更快,因为G ...

  10. Python多进程(process)和多线程(thread)的区别

    目录 一.线程与进程 1.基本概念 2.区别 二.多进程与多线程 1.多进程 (1)Python的多进程编程与multiprocess模块 (2)利用multiprocess模块的Pool类创建多进程 ...

最新文章

  1. Lua学习教程之 可变參数数据打包与解包
  2. 论文浅尝 | 六篇2020年知识图谱预训练论文综述
  3. 线性代数第九版pdf英文_斯坦福CS229机器学习课程的数学基础(线性代数)翻译完成...
  4. Python微调文本顺序对抗朴素贝叶斯算法垃圾邮件分类机制
  5. Appium+Python之批量执行测试用例
  6. 转:windows 蓝屏代码 .
  7. 软件开发 外包_软件开发外包:选择它的理由
  8. IDEA从零到精通(29)之chinese中文汉化插件
  9. Google Chrome谷歌浏览器中安装JsonView插件实现json数据转码、缩进、格式化的方法
  10. mac google浏览器axure插件
  11. android webview 清空内容,Android WebView清空缓存
  12. 希腊字母表及其读音与意义
  13. Python函数及参数
  14. 产品级Flutter开源项目FunAndroid,Provider MVVM的最佳实践
  15. 梅科尔工作室-梁嘉莹-鸿蒙笔记1
  16. python批量下载图片
  17. ORACLE内核参数
  18. 对数函数泰勒级数展开式
  19. SQL39 针对salaries表emp_no字段创建索引idx_emp_no,查询emp_no为10005,使用强制索引。
  20. 安卓、苹果怎么多开微信?微信多开软件

热门文章

  1. vue中替换全局字体
  2. Illustrator绘制萌宠小动物插图教程
  3. @Autowired注解的实现原理
  4. 2011秋冬学生男装搭配技巧
  5. 使用VUX组件库,苹果系统升级至IOS16后样式错乱
  6. 实验室20200314数据处理任务总结
  7. 计算机通用用户名和密码,IUSR_和IWAM_:计算机名帐户的用户名和密码
  8. 我是你的用户 你为什么讨厌我?
  9. 糟糕的横向发展 可穿戴厂商的迷途
  10. SpringBoot @Validated原理解析