-
并发与并行
并发和并行是两个非常容易混淆的概念。他们都可以表示两个或多个任务一起执行,但是偏重点有点不同。并发偏重于多个任务交替执行,而多个任务之间有可能还是串行的。并发是逻辑上的同时发生,而并行是物理上的同时发生,然而并行的偏重点在于同时执行。
并发:指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进行同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行。这就能好比一个人一把铁锹同时挖三个坑,每个坑都挖一下然后去挖下一个,虽然三个坑都变大,但实际上同一时间只有一个坑在被挖。
严格意义上来说,并行的多个任务是真实的同时执行,而对于并发来说,这个过程只是交替的,一会运行任务一,一会运行任务二,系统会不停的在两者间切换,对于外部观察者来说,即使多个任务是串行并发的,也会造成多个任务并行执行的错觉。
并行,指在同一时刻,有多条指令在多个处理器上同时执行,就好像三个人三把铁锹同时在挖三个坑,三个坑一起变大,所以无论从微视还是宏观来看,二者都是一起执行的。 -
线程与进程
开个qq,开了一个进程;开个迅雷,开了一个进程
在qq的这个进程里,传输文字开一个线程。在这个软件运行的过程(在这个进程里),多个工作同时运转,完成了qq的运行,那么这“多个工作”分别有一个线程,所以一个进程管着多个线程,一个进程有且至少有一个线程
线程处理I/O密集型任务
import threading
import time
start_time = time.time()
def foo(something):
print(something)
time.sleep(1)
# 串行两秒
# foo('xxoo')
# foo('xoxo')
# 线程1秒
t1 = threading.Thread(target=foo, args=['xxoo'])
t2 = threading.Thread(target=foo, args=['oxox'])
t1.start()
t2.start()
t1.join()
t2.join()
end_time = time.time()
print(end_time - start_time)
- 线程处理计算密集型任务
import time
import threading
def foo():
num = 0
for i in range(10000000):
num+=i
t1 = threading.Thread(target=foo)
t2 = threading.Thread(target=foo)
start_time = time.time()
t1.start()
t2.start()
t1.join()
t2.join()
#线程 1.1275379657745361
end_time = time.time()
print('线程',end_time - start_time)
#串行时间1.126770257949829
start_time = time.time()
foo()
foo()
end_time = time.time()
print('串行',end_time - start_time)
实际上,解释器被一个全局解释器锁保护着,它确保任何时候都只有一个Python线程执行。 GIL最大的问题就是Python的多线程程序并不能利用多核CPU的优势 (比如一个使用了多个线程的计算密集型程序只会在一个单CPU上面运行)。
在讨论普通的GIL之前,有一点要强调的是GIL只会影响到那些严重依赖CPU的程序(比如计算型的)。 如果你的程序大部分只会涉及到I/O,比如网络交互,那么使用多线程就很合适, 因为它们大部分时间都在等待。实际上,你完全可以放心的创建几千个Python线程, 现代操作系统运行这么多线程没有任何压力,没啥可担心的。
- join & setDaemon
setDaemon(True):
将线程生命为守护线程,必须在start()方法调用之前设置,如果不设置为守护线程程序会被无限挂起,这个方法基本和join是相反的。当我们在程序运行中,执行一个主线程,如果主线程又创建了一个子线程,主线程和子线程就兵分两路,分别运行,那么当主线程完成想退出时,会检查子线程是否完成。如果子线程未完成,则主线程会等待子线程完成后在退出,但是有时候我们需要的是只要主线程完成了,不管子线程是否完成,都要和主线程一起退出,这时就可以用setDaemon方法
join:在子线程完成运行之前,这个子线程的父线程将一致被阻塞
import threading
import time
def music():
for i in range(30):
print('听音乐')
time.sleep(1)
def video():
for i in range(3):
print('看电影')
time.sleep(1)
t1 = threading.Thread(target=music) #生成一个线程实例
t2 = threading.Thread(target=video)
t1.setDaemon(True)
t1.start()
t2.start()
t2.join()
print("电影结束了")
- 此示例展示了多个线程同时操作一个共享资源,所以造成了资源破坏,使用同步锁解决
import time,threading
account_balance = 500
def option_num(num):
global account_balance
balance = account_balance
time.sleep(1)
balance = balance+num
account_balance = balance
t1 = threading.Thread(target=option_num,args=[10000])
t2 = threading.Thread(target=option_num,args=[-300])
t1.start()
t2.start()
t1.join()
t2.join()
print(account_balance)
- 同步锁
如代码,需要操作账户余额的时候,我们给他上一把锁,同一时间只允许一个线程进行操作,等操作完毕在把锁打开,如此就避免了数据安全的问题。(线程共享全局变量)
例一:
import threading
#多线程全局变量问题
#进程系统分配资源的
#cup调度线程没有自己独立的内存空间
#电脑有8个核,运行线程的只有一个核
import threading
import time
from threading import Lock
#多线程同时对一个全局变量操作
a = 100
def func1():
global a
lock.acquire()
for i in range(10000000):
a += 1
lock.release()
print('线程1修改完',a)
def func2():
global a
lock.acquire()
for i in range(10000000):
a += 1
print('线程2修改完',a)
lock.release()
lock = threading.Lock()
t1 = threading.Thread(target=func1)
t2 = threading.Thread(target=func2)
t1.start()
t2.start()
t1.join()
t2.join()
print(a)
例二:
import time,threading
account_balance = 500
r = threading.Lock()#一把锁
def option_num(num):
global account_balance
r.acquire() #上锁
balance = account_balance
time.sleep(1)
balance = balance+num
account_balance = balance
r.release() #开锁
t1 = threading.Thread(target=option_num,args=[10000])
t2 = threading.Thread(target=option_num,args=[-300])
t1.start()
t2.start()
t1.join()
t2.join()
print(account_balance)
- 死锁和递归锁
什么是死锁呢?在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,这时候就会造成死锁,因为系统判断这部分资源都正在使用,所有这两个线程在无外力作用下将一直等待下去。
例子:小明今天去面试,面试官说:“你给我解释一下什么是死锁,我就给你发 offer”。小明一听就不乐意 了,心想你还挺横,于是就说:“你给我发 offer 我就告诉你什么叫死锁。” 诺,这就是死锁,小明和面试官都霸 占有对方的资源不肯松手并且同时在等待对方的资源 - 死锁 解决办法递归锁
import threading,time
lockA = threading.Lock()
lockB = threading.Lock()
def foo1():
lockA.acquire()
print('解释什么是死锁')
time.sleep(1)
lockB.acquire()
print('给你发office')
time.sleep(1)
lockA.release()
lockB.release()
def foo2():
lockB.acquire()
print('给我发office')
time.sleep(1)
lockA.acquire()
print('我就解释什么是死锁')
time.sleep(1)
lockA.release()
lockB.release()
t1 = threading.Thread(target=foo1)
t2 = threading.Thread(target=foo2)
t1.start()
t2.start()
- 递归锁
为了支持在同一线程中多次请求同一资源,python提供了可重入锁:threading.RLock RLock内部维护这一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以背多次acquire,直到一个线程所有的acquire都被release,其他的线程才能获得资源
import threading,time
lockR = threading.RLock()
# lockB = threading.Lock()
def foo1():
lockR.acquire()
print('解释什么是死锁')
time.sleep(1)
lockR.acquire()
print('给你发office')
time.sleep(1)
lockR.release()
lockR.release()
def foo2():
lockR.acquire()
print('给我发office')
time.sleep(1)
lockR.acquire()
print('我就解释什么是死锁')
time.sleep(1)
lockR.release()
lockR.release()
t1 = threading.Thread(target=foo1)
t2 = threading.Thread(target=foo2)
t2.start()
t1.start()
- 线程执行I/O密集型任务的优势(网络请求)
import threading
#通过继承threading类来创建线程
import time
import requests
class RequestThread(threading.Thread):
"""发送request请求"""
#传参数要调用父类的init方法
def __init__(self,url):
threading.Thread.__init__(self)
self.url = url
def run(self):
for i in range(100):
res = requests.get(self.url)
print(f"当前线程{threading.current_thread()}返回的状态码---{res.status_code}")
s_time = time.time()
li1 = []
li = ['q','w','e','r','t']
for i in li:
li = RequestThread('https://www.apple.com/')
li.start()
li1.append(li)
for i in li1:
i.join()
e_time = time.time()
print(e_time-s_time)
s_time = time.time()
for i in range(500):
req = requests.get('https://www.apple.com/')
print(req.status_code)
e_time = time.time()
print(e_time-s_time)
网友评论