概述

进程与线程

进程:进程是资源(CPU、内存等)分配的最小单位,进程有独立的地址空间与系统资源,一个进程可以包含一个或多个线程

线程:线程是CPU调度的最小单位,是进程的一个执行流,线程依赖于进程而存在,线程共享所在进程的地址空间和系统资源,每个线程有自己的堆栈和局部变量

形象的解释:

系统是一个工厂,进程就是工厂里面的车间;

车间的空间大小以及里面的生产工具就是系统分配给进程的资源(CPU、内存等);

车间要完成生产,就需要工人,工人就是线程;

工人可以使用车间的所有资源,就是线程共享进程资源;

工人使用车间内的一个工作间(全局变量,共享内存)工作的时候,为了防止其他工人打扰,会上一把锁,工作完成才会取下,这是线程锁;

有的工作间可以同时容纳多个工人工作,于是就有多把钥匙,每个工人就拿上一把,所有钥匙被取完后,其他工人就只能等着,这是信号量(Semaphore);

有时候工人之间有合作,当一个工作间的工人工作到满足某个条件时,会发出通知并同时退出工作间,将钥匙交给另外符合条件正在等待的工人完成工作,这叫条件同步;

还有一种工作模式,当一个工人完成到某个指标时,会将工作传递给其它等待这个指标触发的工人工作,这叫事件同步

并发与并行

并发:当系统只有一个CPU时,想执行多个线程,CPU就会轮流切换多个线程执行,当有一个线程被执行时,其他线程就会等待,但由于CPU调度很快,所以看起来像多个线程同时执行

并行:当系统有多个CPU时,执行多个线程,就可以分配到多个CPU上同时执行

同步与异步

同步:调用者调用一个功能时,必须要等到这个功能执行完返回结果后,才能再调用其他功能

异步:调用者调用一个功能时,不会立即得到结果,而是在调用发出后,被调用功能通过状态、通知来通告调用者,或通过回调函数处理这个调用

多线程模块

python中主要用threading模块提供对线程的支持

threading模块

threading模块常用函数

  • threading.current_thread(): 返回当前的线程对象。
  • threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
  • threading.active_count(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

Thread类

通过threading.Thread()创建线程对象

主要参数:

threading.Thread(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)

  • group 默认为 None,为了日后扩展 ThreadGroup 类实现而保留。
  • target 是用于 run() 方法调用的可调用对象。默认是 None,表示不需要调用任何方法。
  • name 是线程名称。默认情况下,由 "Thread-N" 格式构成一个唯一的名称,其中 N 是小的十进制数
  • args 是用于调用目标函数的参数元组。默认是 ()
  • kwargs 是用于调用目标函数的关键字参数字典。默认是 {}。
  • daemon表示线程是不是守护线程。

Thread类常用方法与属性

  • run(): 用以表示线程活动的方法。
  • start():启动线程活动。
  • join(timeout=None): 等待至线程中止。这阻塞调用线程直至线程的join() 方法被调用中止-正常退出或者抛出未处理的异常-或者是可选的超时发生。
  • isAlive(): 返回线程是否活动的。
  • getName(): 返回线程名。
  • setName(): 设置线程名。
  • name:线程对象名字
  • setDaemon():设置是否为守护线程

创建多线程

1.通过函数方法创建

import threading
import time def run(sec):
print('%s 线程开始了!' % threading.current_thread().name)
time.sleep(sec)
print('%s 线程结束了!' % threading.current_thread().name) if __name__ == '__main__': print('主线程开始执行:', threading.current_thread().name) s_time = time.time()
# 创建thread对象,target传入线程执行的函数,args传参数
t1 = threading.Thread(target=run, args=(1,))
t2 = threading.Thread(target=run, args=(2,))
t3 = threading.Thread(target=run, args=(3,))
# 使用start()开始执行
t1.start()
t2.start()
t3.start()
# 使用join()来完成线程同步
t1.join()
t2.join()
t3.join() print('主线程执行结束', threading.current_thread().name)
print('一共用时:', time.time()-s_time)

2.通过直接从 threading.Thread 继承创建一个新的子类,并实例化后调用 start() 方法启动新线程,即它调用了线程的 run() 方法

import threading
import time class MyThread(threading.Thread): def __init__(self, sec):
super(MyThread, self).__init__()
self.sec = sec
#重写run()方法,使它包含线程需要做的工作
def run(self):
print('%s 线程开始了!' % threading.current_thread().name)
time.sleep(self.sec)
print('%s 线程结束了!' % threading.current_thread().name) if __name__ == '__main__': print('主线程开始执行', threading.current_thread().name)
s_time = time.time() # 实例化MyThread对象
t1 = MyThread(1)
t2 = MyThread(2)
t3 = MyThread(3)
# 实例化后调用 start() 方法启动新线程,即它调用了线程的 run() 方法
t1.start()
t2.start()
t3.start()
# 使用join()来完成线程同步
t1.join()
t2.join()
t3.join() print('一共用时:', time.time()-s_time)
print('主线程结束执行', threading.current_thread().name)

线程锁

多个线程操作全局变量的时候,如果一个线程对全局变量操作分几个步骤,当还没有得到最后结果时,这个线程就被撤下CPU,使用其他线程继续上CPU操作,最后就会搞乱全局变量数据

import time, threading

balance = 0

def change_it(n):
# 先存后取,结果应该为0:
global balance
balance = balance + n
balance = balance - n def run_thread(n):
for i in range(1000):
change_it(n) #这里重复实验100次
for i in range(100):
t1 = threading.Thread(target=run_thread, args=(5,))
t2 = threading.Thread(target=run_thread, args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
print(balance)

这里可以发现打印出数据会有非0情况

而线程锁就是防止这种情况。每当一个线程a要访问共享数据时,必须先获得锁定;如果已经有别的线程b获得锁定了,那么就让线程a暂停,也就是同步阻塞;等到线程b访问完毕,释放锁以后,再让线程a继续

锁有两种状态:被锁(locked)和没有被锁(unlocked)。拥有acquire()和release()两种方法,并且遵循一下的规则:

  • 如果一个锁的状态是unlocked,调用acquire()方法改变它的状态为locked;
  • 如果一个锁的状态是locked,acquire()方法将会阻塞,直到另一个线程调用release()方法释放了锁;
  • 如果一个锁的状态是unlocked调用release()会抛出RuntimeError异常;
  • 如果一个锁的状态是locked,调用release()方法改变它的状态为unlocked。
import time, threading

balance = 0
lock = threading.Lock() def change_it(n):
# 先存后取,结果应该为0:
global balance
#获取锁,用于线程同步
lock.acquire()
balance = balance + n
balance = balance - n
# 释放锁,开启下一个线程
lock.release() def run_thread(n):
for i in range(1000):
change_it(n) #这里重复实验100次
for i in range(100):
t1 = threading.Thread(target=run_thread, args=(5,))
t2 = threading.Thread(target=run_thread, args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
print(balance)

锁的好处就是确保了某段关键代码只能由一个线程从头到尾完整地执行,坏处当然也很多,首先是阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了。其次,由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁,导致多个线程全部挂起,既不能执行,也无法结束,只能靠操作系统强制终止。

条件同步

条件同步机制是指:一个线程等待特定条件,而另一个线程发出特定条件满足的信号。

解释条件同步机制的一个很好的例子就是生产者/消费者(producer/consumer)模型。生产者随机的往列表中“生产”一个随机整数,而消费者从列表中“消费”整数。

import threading
import random
import time class Producer(threading.Thread):
"""
向列表中生产随机整数
""" def __init__(self, integers, condition):
"""
构造器 @param integers 整数列表
@param condition 条件同步对象
"""
super(Producer, self).__init__()
self.integers = integers
self.condition = condition def run(self):
"""
实现Thread的run方法。在随机时间向列表中添加一个随机整数
"""
while True:
integer = random.randint(0, 256)
self.condition.acquire() # 获取条件锁
self.integers.append(integer)
print ('%d appended to list by %s' % (integer, self.name))
self.condition.notify() # 唤醒消费者线程
self.condition.release() # 释放条件锁
time.sleep(1) # 暂停1秒钟 class Consumer(threading.Thread):
"""
从列表中消费整数
""" def __init__(self, integers, condition):
"""
构造器 @param integers 整数列表
@param condition 条件同步对象
"""
super(Consumer, self).__init__()
self.integers = integers
self.condition = condition def run(self):
"""
实现Thread的run()方法,从列表中消费整数
"""
while True:
self.condition.acquire() # 获取条件锁
while True:
if self.integers: # 判断是否有整数
integer = self.integers.pop()
print ('%d popped from list by %s' % (integer, self.name))
break
self.condition.wait() # 等待商品,并且释放资源
self.condition.release() # 最后释放条件锁 if __name__ == '__main__':
integers = []
condition = threading.Condition()
t1 = Producer(integers, condition)
t2 = Consumer(integers, condition)
t1.start()
t2.start()
t1.join()
t2.join()

类似于线程锁,通过给生产者和消费者传入同个条件对象后,他们都会通过acquire()获取条件锁;如上,消费者获取条件锁,如果没有锁,就会去判断integers中是否有整数,没有就会触发wait()方法,等待生产者线程notify()的唤醒;wait()方法会释放底层锁,并且阻塞,

直到在另外一个线程中调用同一个条件对象的notify() 或 notify_all() 唤醒它,或者超时发生。生产者则就是获取锁,向integers添加值,唤醒其他wait()的线程,释放锁的循环过程。

事件同步(Event)

基于事件的同步是指:一个线程发送/传递事件,另外的线程等待事件的触发。与条件同步类似,只是少了我们自己添加锁的步骤。

同样用消费者与生产者为例

import threading
import random
import time class Producer(threading.Thread):
"""
向列表中生产随机整数
""" def __init__(self, integers, event):
"""
构造器 @param integers 整数列表
@param event 事件同步对象
"""
super(Producer, self).__init__()
self.integers = integers
self.event = event def run(self):
"""
实现Thread的run方法。在随机时间向列表中添加一个随机整数
"""
while True:
integer = random.randint(0, 256)
self.integers.append(integer)
print('%d appended to list by %s' % (integer, self.name))
print('event set by %s' % self.name)
self.event.set() # 设置事件
self.event.clear() # 发送事件
print('event cleared by %s' % self.name)
time.sleep(1) class Consumer(threading.Thread):
"""
从列表中消费整数
""" def __init__(self, integers, event):
"""
构造器 @param integers 整数列表
@param event 事件同步对象
"""
super(Consumer, self).__init__()
self.integers = integers
self.event = event def run(self):
"""
实现Thread的run()方法,从列表中消费整数
"""
while True:
self.event.wait() # 等待事件被触发
try:
integer = self.integers.pop()
print('%d popped from list by %s' % (integer, self.name))
except IndexError:
# catch pop on empty list
time.sleep(1) if __name__ == '__main__':
integers = []
event=threading.Event()
t1 = Producer(integers, event)
t2 = Consumer(integers, event)
t1.start()
t2.start()
t1.join()
t2.join()

事件对象的set()方法会将内部标志设置为true,并且唤醒其他阻塞的线程;clear()方法则是将内部标志设置为False;wait()方法当内部标准为false时就阻塞,true则不阻塞且wait()内部什么都不操作

队列(Queue)

队列可以看作事件同步与条件同步的简单版,线程A可以往队列里面存数据,线程B则可以从队列里面取,不用关心同步以及锁的问题,主要方法有:

  • put(): 向队列中添加一个项
  • get(): 从队列中删除并返回一个项
  • task_done(): 当某一项任务完成时调用,表示前面排队的任务已经被完成
  • join(): 阻塞直到所有的项目都被处理完
import threading
import queue
import random
import time class Producer(threading.Thread): def __init__(self, queue):
super(Producer,self).__init__()
self.queue = queue def run(self):
while True:
integer = random.randint(0, 1000)
self.queue.put(integer)
print('%d put to queue by %s' % (integer, self.name))
time.sleep(1) class Consumer(threading.Thread): def __init__(self, queue):
super(Consumer, self).__init__()
self.queue = queue def run(self):
while True:
integer = self.queue.get()
print('%d popped from list by %s' % (integer, self.name))
self.queue.task_done() if __name__ == '__main__': queue = queue.Queue()
t1 = Producer(queue)
t2 = Consumer(queue)
t1.start()
t2.start()
t1.join()
t2.join()

参考:

http://yoyzhou.github.io/blog/2013/02/28/python-threads-synchronization-locks/

https://www.cnblogs.com/cnkai/p/7506476.html

https://docs.python.org/zh-cn/3/library/threading.html

python多线程总结的更多相关文章

  1. python多线程学习记录

    1.多线程的创建 import threading t = t.theading.Thread(target, args--) t.SetDeamon(True)//设置为守护进程 t.start() ...

  2. python多线程编程

    Python多线程编程中常用方法: 1.join()方法:如果一个线程或者在函数执行的过程中调用另一个线程,并且希望待其完成操作后才能执行,那么在调用线程的时就可以使用被调线程的join方法join( ...

  3. Python 多线程教程:并发与并行

    转载于: https://my.oschina.net/leejun2005/blog/398826 在批评Python的讨论中,常常说起Python多线程是多么的难用.还有人对 global int ...

  4. python多线程

    python多线程有两种用法,一种是在函数中使用,一种是放在类中使用 1.在函数中使用 定义空的线程列表 threads=[] 创建线程 t=threading.Thread(target=函数名,a ...

  5. python 多线程就这么简单(转)

    多线程和多进程是什么自行google补脑 对于python 多线程的理解,我花了很长时间,搜索的大部份文章都不够通俗易懂.所以,这里力图用简单的例子,让你对多线程有个初步的认识. 单线程 在好些年前的 ...

  6. python 多线程就这么简单(续)

    之前讲了多线程的一篇博客,感觉讲的意犹未尽,其实,多线程非常有意思.因为我们在使用电脑的过程中无时无刻都在多进程和多线程.我们可以接着之前的例子继续讲.请先看我的上一篇博客. python 多线程就这 ...

  7. python多线程监控指定目录

    import win32file import tempfile import threading import win32con import os dirs=["C:\\WINDOWS\ ...

  8. python多线程ssh爆破

    python多线程ssh爆破 Python 0x01.About 爆弱口令时候写的一个python小脚本,主要功能是实现使用字典多线程爆破ssh,支持ip表导入,字典数据导入. 主要使用到的是pyth ...

  9. 【python,threading】python多线程

    使用多线程的方式 1.  函数式:使用threading模块threading.Thread(e.g target name parameters) import time,threading def ...

  10. <转>Python 多线程的单cpu与cpu上的多线程的区别

    你对Python 多线程有所了解的话.那么你对python 多线程在单cpu意义上的多线程与多cpu上的多线程有着本质的区别,如果你对Python 多线程的相关知识想有更多的了解,你就可以浏览我们的文 ...

随机推荐

  1. Python玩转人工智能最火框架 TensorFlow应用实践 ☝☝☝

    Python玩转人工智能最火框架 TensorFlow应用实践 (一个人学习或许会很枯燥,但是寻找更多志同道合的朋友一起,学习将会变得更加有意义✌✌) 全民人工智能时代,不甘心只做一个旁观者,那就现在 ...

  2. hibernate之小白一

    关于hibernate框架,以下是我自己的见解,每个人的理解各不同,希望各位读者根据自己的需要来查询自己想要的.以下我来给你们分享我学习hibernate的一些理论和实践: 首先我们来了解一下hibe ...

  3. SpringBoot系列教程web篇之过滤器Filter使用指南扩展篇

    前面一篇博文介绍了在 SpringBoot 中使用 Filter 的两种使用方式,这里介绍另外一种直接将 Filter 当做 Spring 的 Bean 来使用的方式,并且在这种使用方式下,Filte ...

  4. win10系统plsql卡顿、菜单闪烁解决办法

    右键快捷方式--属性--兼容性: 设置为以win7模式运行,以管理员模式运行.如图:

  5. 微信小程序实现九宫格切图,保存功能!

    效果如下图: 代码如下: <view class='sudoku'> <scroll-view scroll-x scroll-y class='canvas-box'> &l ...

  6. VSCode 安装 code 命令

    VSCode 提供 code 命令直接从命令行中打开文件目录,此时需要先安装 code 命令. 1.首先打开 VSCode 2.使用 command + shift + p (注意window 下使用 ...

  7. 图像处理 - ImageMagick 简单介绍与案例

    在客户端我们可以用 PhotoShop 等 GUI 工具处理静态图片或者动态 GIF 图片,不过在服务器端对于 WEB 应用程序要处理图片格式转换,缩放裁剪,翻转扭曲,PDF解析等操作, GUI 软件 ...

  8. 学习python3高阶函数笔记和demo

    python的高阶函数的定义是:一个函数接收另一个函数作为参数,这种函数就称之为高阶函数 举一个最简单的例子: def text(a,b,c): return c(a)+c(b) print( tex ...

  9. 百万年薪python之路 -- HTML标签

    HTML标签 html标签分类 html标签又叫做html元素,它分为块级元素和内联元素(也可以叫做行内元素),都是html规范中的概念. 标题 h1 h2 h3 h4 h5 h6 列表 ol ul ...

  10. Leetcode(1)两数之和

    Leetcode(1)两数之和 [题目表述]: 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标.你可以假设每种输入只会对应一 ...