并发就是多线程轮流使用同一进程的资源
进程里面可以存着多个线程,多个线程共用进程资源,因此存在多个线程争夺进程资源的问题
1. 线程状态:
新建,就绪,运行,阻塞,结束
阻塞完之后,会回到就绪上来
import threading from time import sleep def download(n): images = ['1.jpg', '2.jpg', '3.jpg'] for image in images: print('正在下载:', image) sleep(n) print('下载{}成功!:'.format(image)) def listen_music(n): musics = ['大碗宽面', '土耳其冰淇淋', '烤面筋', '人民广场炸鸡'] for music in musics: sleep(n) print('正在听{}歌!'.format(music)) if __name__ == '__main__': # 创建线程对象 t = threading.Thread(target=download, name='aaa', args=(1, )) t.start() t1 = threading.Thread(target=listen_music, name='bbb', args=(0.5, )) t1.start()
线程是可以共享全局变量
import threading money = 1000 def run1(): global money for i in range(100): money -= 1 if __name__ == '__main__': th1 = threading.Thread(target=run1) th2 = threading.Thread(target=run1) th3 = threading.Thread(target=run1) th4 = threading.Thread(target=run1) th1.start() th2.start() th3.start() th4.start() th1.join() th2.join() th3.join() th4.join() print('money:', money)
输出:
2.线程同步
GIL(Global Interpreter Lock)全局解释器锁
先明确一个问题,Python中的多线程是假的多线程! 为什么这么说,我们先明确一个概念,全局解释器锁(GIL)。
Python代码的执行由Python虚拟机(解释器)来控制。Python在设计之初就考虑要在主循环中,同时只有一个线程在执行,就像单CPU的系统中运行多个进程那样,内存中可以存放多个程序,但任意时刻,只有一个程序在CPU中运行。同样地,虽然Python解释器可以运行多个线程,只有一个线程在解释器中运行。
对Python虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同时只有一个线程在运行。在多线程环境中,Python虚拟机按照以下方式执行。
1.设置GIL。
2.切换到一个线程去执行。
3.运行。
4.把线程设置为睡眠状态。
5.解锁GIL。
6.再次重复以上步骤。
引子:
import threading n = 0 def task1(): global n for i in range(1000000): n += 1 print('-----> task1中的n值是:', n) def task2(): global n for i in range(1000000): n += 1 print('-----> task2中的n值是:', n) if __name__ == '__main__': th1 = threading.Thread(target=task1) th2 = threading.Thread(target=task2) th1.start() th2.start() th1.join() th2.join() print('n:', n)
这个问题,就是线程异步的问题,CPU轮流执行各个线程出现的问题,因此需要一个锁,即线程同步(出现执行速度慢的问题)
只要有共享数据,就要使用线程同步
Python底层只要用线程,默认加锁(GIL),只要有锁,就没办法达到真正的多线程
Python底层针对只要运算量达到某个程度,就自动释放GIL,因此出现的n最后的值不是2百万
在数据共享方面进程与线程的区别:
进程是每个进程中都有一份
线程是共用一个数据 ---》数据安全性问题
进程:一般使用于计算密集型(计算量大的,快速计算的)
线程:一般耗时操作的时候用,如下载,爬虫,IO操作
import threading import time lock = threading.Lock() lyst = [0] * 10 def task1(): # 获取线程锁,如果已经加上锁,则等待锁的释放 lock.acquire() # 阻塞 for i in range(len(lyst)): lyst[i] = 1 time.sleep(0.5) lock.release() # 释放锁,只要不释放,其他的线程都无法进入运行状态 def task2(): lock.acquire() # 请求得到锁 for i in range(len(lyst)): print('---->', lyst[i]) time.sleep(0.5) lock.release() if __name__ == '__main__': t1 = threading.Thread(target=task1) t2 = threading.Thread(target=task2) t2.start() t1.start() t2.join() t1.join() print(lyst)
3.死锁
开发过程中使用线程,在线程间共享多个资源的时候,如果两个线程分别占有一部分资源,并且同时等待对方的资源,就会造成死锁。
尽管死锁很少发生,但一旦发生就会造成应用的停止响应,程序不做任何事情
一定要避免出现死锁,解决方式:
1.重构代码
2.在acquire(timeout=5)加入timeout,只要超时,就将锁释放
from threading import Thread, Lock import time lockA = Lock() lockB = Lock() class MyThread(Thread): def run(self): # start if lockA.acquire(): # 如果可以获取到锁则返回True print(self.name+'获取了A锁') # 系统默认name时Thread-1 time.sleep(0.1) if lockB.acquire(): # B锁已经被thread1拿了,因此就会阻塞 print(self.name+'又获取了B锁,原来还有A锁') lockB.release() lockA.release() class MyThread1(Thread): def run(self): # start if lockB.acquire(): # 如果可以获取到锁则返回True print(self.name+'获取了B锁') # 系统默认name时Thread-2 time.sleep(0.1) if lockA.acquire(): print(self.name+'又获取了A锁,原来还有B锁') lockA.release() lockB.release() if __name__ == '__main__': th = MyThread() th1 = MyThread1() th.start() th1.start()
解决
from threading import Thread, Lock import time lockA = Lock() lockB = Lock() class MyThread(Thread): def run(self): # start if lockA.acquire(): # 如果可以获取到锁则返回True print(self.name+'获取了A锁') # 系统默认name时Thread-1 time.sleep(0.1) if lockB.acquire(timeout=3): # B锁已经被thread1拿了,因此就会阻塞 print(self.name+'又获取了B锁,原来还有A锁') lockB.release() lockA.release() class MyThread1(Thread): def run(self): # start if lockB.acquire(): # 如果可以获取到锁则返回True print(self.name+'获取了B锁') # 系统默认name时Thread-2 time.sleep(0.1) if lockA.acquire(timeout=3): print(self.name+'又获取了A锁,原来还有B锁') lockA.release() lockB.release() if __name__ == '__main__': th = MyThread() th1 = MyThread1() th.start() th1.start()