python并发编程之threading线程(一)
进程是系统进行资源分配最小单元,线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.进程在执行过程中拥有独立的内存单元,而多个线程共享内存等资源。
系列文章
threading模块创建线程
import threading
from threading import Thread
def test(x):
print('this is {}'.format(x))
time.sleep(2)
def get_thread(number=5):
l_thread = (Thread(target=test, args=(i,)) for i in range(number))
for t in l_thread:
print(t)
t.start() # 启动线程开始执行
print(len(threading.enumerate()))
if __name__ == '__main__':
get_thread(5)
# 结果
<Thread(Thread-1, initial)>
this is 0
<Thread(Thread-2, initial)>
this is 1
<Thread(Thread-3, initial)>
this is 2
<Thread(Thread-4, initial)>
this is 3
<Thread(Thread-5, initial)>
this is 4
6
通过以上可知,我们只需要创建一个Thread对象,并运行start方法,解释器就会创建一个子进程执行我们的target,我们创建了5个线程,但是使用threading.enumerate查看线程的数量发现有6个线程,因为当前在执行的还有一个主线程。主线程会默认等待所有的子线程结束后再结束。
- 我们还有另外一种创建线程的方式
import threading
from threading import Thread
class MyThread(Thread):
def __init__(self, x):
super().__init__()
self.x = x
def run(self):
print('this is {}'.format(self.x))
time.sleep(2)
def get_thread1(number=5):
l_thread = (MyThread(i) for i in range(number))
for t in l_thread:
print(t.name)
t.start()
print(len(threading.enumerate()))
if __name__ == '__main__':
get_thread1(5)
Thread对象有一个run方法,它就是我们需要执行的目标函数,所以我们可以通过继承Thread对象,重写run方法,将我们的目标代码放置在run方法中。
Thread对象分析
class Thread:
def __init__(self, group=None, target=None, name=None,
args=(), kwargs=None, *, daemon=None):
pass
# Thread类是python用来创建线程的类,
group:扩展保留字段;
target:目标代码,一般是我们需要创建线程执行的目标函数。
name:线程的名字,如果不指定会自动分配一个;
args:目标函数的普通参数;
kwargs:目标函数的键值对参数;
daemon:设置线程是否为守护线程,即是前台执行还是后台执行,默认是非守护线程,当daemon=True时,子线程为守护线程,此时主线程不会等待子线程,如果主线程完成会强制杀死所有的子线程然后退出。
# 方法
start():创建一个子线程并执行,该方法一个Thread实例只能执行一次,其会创建一个线程执行该类的run方法。
run():子线程需要执行的代码;
join():主线程阻塞等待子线程直到子线程结束才继续执行,可以设置等待超时时间timeout.
ident():线程标识符,线程未启动之前为None,启动后为一个int;
is_alive():查看子线程是否还活着你返回一个布尔值。
daemon:判断是否是守护线程;
线程非安全与锁
多个线程之间可以共享内存等资源,使得多个线程操作同一份资源的时候可能导致资源发生破坏,即线程非安全。
number = 100
class MyThread(Thread):
def run(self):
for i in range(1000000):
global number
number += 1
print(number)
def get_thread1(number=5):
l_thread = (MyThread() for i in range(number))
for t in l_thread:
t.start()
if __name__ == '__main__':
get_thread1(5)
# 结果
1439426
1378835
2241060
2533150
3533150
上例可知,如果是同步运算的话,最终number的结果应该为5000100,但显然不是。原因是如果线程1取得number=100时,线程切换到线程2,又取得number=100,加1赋值给number=101;如果,又切换回线程1,number加1也是101;相当于执行了两次加1的操作,然而number=101.这就是多线程的线程非安全!
怎么解决这个问题呢?我们看到上述代码中number += 1是核心代码,这个地方随意切换线程就会造成数据破坏,因此只要我们能够设置代码每次执行到这里的时候不允许切换线程就行了。这就是锁的由来。
用锁加入上述代码:
number = 100
mutex = threading.Lock() # 创建锁对象
class MyThread(Thread):
def run(self):
global number
for i in range(1000000):
y = mutex.acquire() # 获取锁
if y: # 拿到锁就执行下面
number += 1
mutex.release() # 释放锁
print(number)
def get_thread1(number=5):
l_thread = (MyThread() for i in range(number))
for t in l_thread:
t.start()
if __name__ == '__main__':
get_thread1(5)
# 结果:
4481177
4742053
4869413
4973771
5000100
可知最后的结果符合预期,threading模块中定义了Lock类,可以很方便实现锁机制,每次执行核心代码之前先去获取锁,拿到了才能执行,拿不到默认阻塞等待。
#创建锁
mutex = threading.Lock()
#锁定
mutex.acquire(blocking=True) # blocking=True,默认线程阻塞等待;如果blocking=False,线程不会等待,即上例中y会返回False,继续执行下面的代码,最后的结果不会符合预期
#释放
mutex.release()
- 小结:
加锁之后,锁住的那段代码变成了单线程,阻止了多线程并发执行,效率下降了;
锁可以有多个,如果不同的线程持有不同的锁并相互等待的话,就会造成死锁;
python的多线程问题远不止如此,还有一个历史遗留问题-全局锁。
死锁
如果一段代码存在两个锁的话,可能会出现死锁现象,一旦出现死锁,系统就会卡死。
number = 100
mutex1 = threading.Lock() # 创建锁对象
mutex2 = threading.Lock()
class MyThread1(Thread):
def run(self):
global number
for i in range(1000):
if mutex1.acquire(): # 拿到锁就执行下面
number += 1
if mutex2.acquire():
print('this is mutex2')
mutex2.release()
mutex1.release() # 释放锁
print(number)
class MyThread2(Thread):
def run(self):
global number
for i in range(1000):
if mutex2.acquire(): # 拿到锁就执行下面
number += 1
if mutex1.acquire():
print('this is mutex2')
mutex1.release()
mutex2.release() # 释放锁
print(number)
def get_thread1():
l_thread = (MyThread1(), MyThread2())
for t in l_thread:
t.start()
if __name__ == '__main__':
get_thread1()
一般解决死锁的办法是尽量不使用多个锁,或设计程序时避免死锁,或为锁添加超时等待。
全局锁(GIL)
全局锁的前世今生不是一两句话能讲完的。可参考:Python全局解释器锁
总结一下就是:
- 全局锁的存在是为了保护多线程对数据的安全访问;
- 对于任何Python程序,不管有多少的处理器内核,任何时候都总是只有一个线程在执行;
- 全局锁的存在使得一般情况下多线程比单线程的执行速度慢;
- python程序只有在io密集时多线程代码效率有所提高,所以不推荐使用多线程而是多进程;更好的替代方案为协程;
number = 100
number1 = 100
mutex = threading.Lock()
class MyThread(Thread):
def run(self):
global number
t1 = time.time()
for i in range(1000000):
y = mutex.acquire() # 获取锁
if y: # 拿到锁就执行下面
number += 1
mutex.release() # 释放锁
t2 = time.time()
print(t2-t1)
def get_thread1(number=5):
l_thread = (MyThread() for i in range(number))
for t in l_thread:
t.start()
def get_thread2(n=5):
global number1
for i in range(1000000*n):
number1 += 1
print(number1)
if __name__ == '__main__':
get_thread1()
t2 = time.time()
get_thread2()
t3 = time.time()
print(t3-t2)
可知多线程的执行时间远远大于单线程。
结论
python最好避免使用多线程,而用多进程代替多线程;
协程是多线程的很好的替代方案。
参考:
python并发编程之threading线程(一)的更多相关文章
- python并发编程之Queue线程、进程、协程通信(五)
单线程.多线程之间.进程之间.协程之间很多时候需要协同完成工作,这个时候它们需要进行通讯.或者说为了解耦,普遍采用Queue,生产消费模式. 系列文章 python并发编程之threading线程(一 ...
- python并发编程之gevent协程(四)
协程的含义就不再提,在py2和py3的早期版本中,python协程的主流实现方法是使用gevent模块.由于协程对于操作系统是无感知的,所以其切换需要程序员自己去完成. 系列文章 python并发编程 ...
- python并发编程之asyncio协程(三)
协程实现了在单线程下的并发,每个协程共享线程的几乎所有的资源,除了协程自己私有的上下文栈:协程的切换属于程序级别的切换,对于操作系统来说是无感知的,因此切换速度更快.开销更小.效率更高,在有多IO操作 ...
- python并发编程之multiprocessing进程(二)
python的multiprocessing模块是用来创建多进程的,下面对multiprocessing总结一下使用记录. 系列文章 python并发编程之threading线程(一) python并 ...
- Python核心技术与实战——十七|Python并发编程之Futures
不论是哪一种语言,并发编程都是一项非常重要的技巧.比如我们上一章用的爬虫,就被广泛用在工业的各个领域.我们每天在各个网站.App上获取的新闻信息,很大一部分都是通过并发编程版本的爬虫获得的. 正确并合 ...
- python并发编程之IO阻塞基础知识点
IO模型 解决IO问题的方式方法 问题是:IO操作阻塞程序执行 解决的也仅仅是网络IO操作 一般数据传输经历的两个阶段,如图: IO阻塞模型分类: 阻塞IO 非阻塞IO 多路复用IO 异步IO(爬 ...
- Python核心技术与实战——十八|Python并发编程之Asyncio
我们在上一章学习了Python并发编程的一种实现方法——多线程.今天,我们趁热打铁,看看Python并发编程的另一种实现方式——Asyncio.和前面协程的那章不太一样,这节课我们更加注重原理的理解. ...
- python 多线程编程之threading模块(Thread类)创建线程的三种方法
摘录 python核心编程 上节介绍的thread模块,是不支持守护线程的.当主线程退出的时候,所有的子线程都将终止,不管他们是否仍在工作. 本节开始,我们开始介绍python的另外多线程模块thre ...
- Python并发编程之IO模型
目录 IO模型介绍 阻塞IO(blocking IO) 非阻塞IO(non-blocking IO) IO多路复用 异步IO IO模型比较分析 selectors模块 一.IO模型介绍 Stevens ...
随机推荐
- Javascript中判断变量是数组还是对象(array还是object)
怎样判断一个JavaScript变量是array还是obiect? 答案: 1.如果你只是用typeof来检查该变量,不论是array还是object,都将返回‘objec'. 此问题的一个可行的答案 ...
- luogu 1344 追查坏牛奶(最小割)
第一问求最小割. 第二问求割边最小的最小割. 我们直接求出第二问就可以求出第一问了. 对于求割边最小,如果我们可以把每条边都附加一个1的权值,那么求最小割是不是会优先选择1最少的边呢. 但是如果直接把 ...
- hive 一次性命令
1.用hive查询,而不进入hive cli,查询后的值可以保存到文件中 #使用参数-e [hadoop@bigdata-senior01 ~]$ hive -e "select * fro ...
- 【刷题】清橙 A1295 necklace
试题来源 清华大学2011年百名信息学优秀高中学子夏令营 问题描述 有人打算送给你一条宝石项链,包含了N颗五颜六色(一共有M种颜色)的宝石.因为本问题中你只关心每个宝石的颜色,而且项链现在两头还没有接 ...
- [NOI2011]兔兔与蛋蛋游戏 二分图博弈
题面 题面 题解 通过观察,我们可以发现如下性质: 可以看做是2个人在不断移动空格,只是2个人能移动的边不同 一个位置不会被重复经过 : 根据题目要求,因为是按黑白轮流走,所以不可能重复经过一个点,不 ...
- 卷积 & 杜教筛
目录 卷积 杜教筛 前言:发现最近都没怎么写博客,,,赶紧发篇以前记的笔记凑凑数 卷积 卷积定义: 如果有数论函数\(f, g\), 那么它们卷积的第\(n\)项为\((f * g) (n)\),设这 ...
- BZOJ 1070 修车 【费用流】
Description 同一时刻有N位车主带着他们的爱车来到了汽车维修中心.维修中心共有M位技术人员,不同的技术人员对不同 的车进行维修所用的时间是不同的.现在需要安排这M位技术人员所维修的车及顺序, ...
- Active Directory中获取域管理员权限的攻击方法
Active Directory中获取域管理员权限的攻击方法 译:by backlion 0x00 前言 攻击者可以通过多种方式在Active Directory中获得域管理员权限, ...
- CentOS 7.0 作为服务器注意事项
配置防火墙,开启80端口.3306端口: CentOS 7.0默认使用的是firewall作为防火墙 关闭firewall: systemctl stop firewalld.service #停止 ...
- 使用Hexo搭建GitHub博客(2018年Mac版)
关于本文 本文仅记录自己学习搭建Hexo博客之时,搭建过程中掉坑的历程总结,对零基础起步的观众朋友可能缺乏某些基础技术的指导,请优先食用下述两篇优质教程: [2018更新]小白独立搭建博客-Githu ...