文章目录
一、多线程提要
什么是线程:
线程:就是操作系统能够进行运算调度的最小单位
1.什么是多线程
多线程:原本需要按先后执行的两个程序,在同一时刻同时进行,每个在同时执行的程序,称为一个线程。
在实际运行中,因为CPU同一时间只能执行一项运算,这时CPU就会将两个程序的执行进行拆分,即:每过极其短暂的一段时间,交替执行不同的两个程序,从视觉上,给人一种同时进行的感觉。
2.python多线程须知:
(1) python多线程弊端
1.在python 中因为GIL(全局解释器锁)的存在,多线程运算中,python仅会将一个线程调入解释器进行操作,即:仅会调用CPU的一个核进行数据操作,即使计算机的CPU有两个,四个,甚至八个以上多核,依然如此。可以理解为,python并不能像java等等语言一样,在多线程中,同时调用多核,实现更加完善的多线程。
2.受单核操作的影响,python在多线程执行计算密集型代码(例如同时执行两个计算量十分庞大的方法)时,通常执行效率在理论上和单线程是并驾齐驱的,甚至因为每次需要暂停记录一次程序状态,让线程能在下一次交替后成功继续向下执行,产生的多余消耗,使得python在多线程执行计算密集型代码时,执行效率甚至不如单线程操作(这个问题在python3.5版本之前尤为明显,多线程执行相同计算密集型代码时(例如1加到1亿),运行效率比起单线程运行,差时甚至能多上一倍时间。当然,现版本python已将这一问题进行了许多的优化。)
(2)python多线程利用
1.相比多线程执行计算密集型代码,python在多线程运算I/O密集型代码时,依然是十分有效的
2.可以使用多进程简单解决多线程弊端问题
python官方原话:
However, threading is still an appropriate model if you want to run multiple I/O-bound tasks simultaneously.
二、多线程(threading模块)
1.threading:
代码案例中使用过的方法列表:
import threading #导入模块
threading.Thread(target = <方法名>, args = (<方法参数>,)) #创建threading对象
.setdaemon #守护线程
.start() #开始线程
.join() #阻塞线程直到线程执行完毕
代码案例:
import threading
import time
start = time.time()
def ret(n):
num = 0
for i in range(n):
num+=i
print(num)
def bet(n):
num = 0
for i in range(n):
num+=i
print(num)
t1 = threading.Thread(target=ret,args=(100000000,)) #创建多线程对象,target=ret(传入指定方法) args=(100000000,) (给指定方法传入参数,需要以元祖形式存放)
t2 = threading.Thread(target=bet,args=(100000000,))
#t1.setDaemon() #setDaemon:守护进程,被守护进程将不会被等待,即如果其他进程已经提前执行结束,那么被守护进程将被中断
t1.start() #开始进程
t2.start()
#t1.join() #阻塞线程直到线程执行完毕
#让两个线程加入到主线程中,使得当两个线程都执行完毕后,再执行主线程中的print('这就是多线程'),否则主线程程序将和两个支线程同时执行
#t2.join()
#print('这就是多线程')
end = time.time()
print(end-start) #打印总执行时间
2.类创建多线程对象
利用继承threading.Thread类,来自主使用类创建多线程对象。
import threading
import time
start = time.time()
class MyThread(threading.Thread):
def __init__(self,num):
threading.Thread.__init__(self) #
self.num = num
def run(self): #该方法继承至threading.Thread,重写后,创建对象,方法将自动执行run方法
num1 = 0
for i in range(self.num):
num1+=i
print(num1)
if __name__ == '__main__':
t1 = MyThread(1000)
t2 = MyThread(5000)
t1.start()
t2.start()
t1.join()
t2.join()
end = time.time()
print(end-start)
3.threading.Lock() 为线程加锁
代码案例方法运用列表:
threading.Lock() #创建锁对象
.acquire() #设置锁
.release() #释放锁
代码案例:
import threading
num = 100
def run():
r.acquire() #开头:使得当前字段在多线程时,依然只能串行执行,即单线程执行
global num
print('...')
temp = num
num = temp-1
r.release() #结尾:释放锁,即:解锁
r = threading.Lock() # 创建一个锁的对象
for i in range(100):
t1 = threading.Thread(target=run)
t1.start()
t1.join()
print(num)
4.死锁现象(该现象需要避免,解析在下方)
import threading
import time
class Mythread(threading.Thread):
def setAB(self):
lockA.acquire()
print('这是锁A...')
time.sleep(3)
lockB.acquire()
print('这是锁B...')
lockB.release()
lockA.release()
def setBA(self):
lockB.acquire()
print('这是锁B...')
time.sleep(5)
lockA.acquire()
print('这是锁A...')
lockA.release()
lockB.release()
def run(self):
self.setAB()
self.setBA()
if __name__ == '__main__':
lockA = threading.Lock()
lockB = threading.Lock()
thread = []
for i in range(5):
thread.append(Mythread())
for t in thread:
t.start()
for t in thread:
t.join()
输出:
这是锁A...
这是锁B...
这是锁B...
这是锁A... #此时程序卡死,不再继续执行
解析上方代码:当线程开始执行时,多线程同时调用run方法,然后执行setAB,setBA方法,因为setAB,setBA中的time.sleep方法存在,两者的调用存在时间差,即存在某个时刻,一个线程在调用setAB方法时,另一个线程正在进行setBA。
setAB,先创建A锁,再创建B锁,释放B锁,再释放A锁
setBA,先创建B锁,再创建A锁,释放A锁,再释放B锁
当setAB与setBA同时执行时,就会发生,setAB需要B锁时,setBA正在使用B锁,setBA需要A锁时,setAB正在使用A锁,此时因为无法执行下方的释放语句,程序将卡主,无法继续向下执行,这就是死锁现象
5.解决死锁现象的递归锁
代码案例方法运用列表:
threading.RLock() #创建递归锁对象
代码案例:
import threading
import time
class Mythread(threading.Thread):
def setAB(self):
lock.acquire()
print('这是锁A...')
time.sleep(3)
lock.acquire()
print('这是锁B...')
lock.release()
lock.release()
def setBA(self):
lock.acquire()
print('这是锁B...')
time.sleep(5)
lock.acquire()
print('这是锁A...')
lock.release()
lock.release()
def run(self):
self.setAB()
self.setBA()
if __name__ == '__main__':
lock = threading.RLock() #threading.RLock() 递归锁
thread = []
for i in range(5):
thread.append(Mythread())
for t in thread:
t.start()
for t in thread:
t.join()
递归锁:相当于是在一个锁上增加了一个计数器,在一个线程中,每次调用递归锁(lock.acquire()),不释放(lock.release())时,计数器从0向上加一,每次释放锁,计数器减一,当计数器为0时,其他线程可继续使用该锁。
输出:
这是锁A...
这是锁B...
这是锁B...
这是锁A...
这是锁A...
这是锁B...
这是锁B...
这是锁A...
这是锁A...
这是锁B...
这是锁B...
这是锁A...
这是锁A...
这是锁B...
这是锁B...
这是锁A...
这是锁A...
这是锁B...
这是锁B...
这是锁A...
Process finished with exit code 0
6.为什么要使用锁
当多个线程需要同时调用一个数据时,可能发生源数据使用不当的现象。
例如:
A有500块钱,B有800块钱,此时A向B转账100块,B刚好也想转账给A200块,正常情况下,应该发生,A减掉100块,B减掉200块后,A400块再拿到B的200块,B600块,再拿到A的100块。最后A为600块,B为700块。
但是如果多线程操作情况下,可能发生,某两个执行转账转入的线程,同时拿到了还没减去转出金额的A的500块,B的800块,此时还没减去转出的钱,就已经加上了后来转来的100块和200块。
即使后来执行了转出减掉了双方多出的非正常余额的语句,但是从逻辑上来说,这是不正确的,转账仅仅只是一个案例,放在其他地方时,程序就可能报错,此时就可以给语句加上线程锁,例如给转出,转入加上锁,使得转出,转入只能串行(单线程)执行,避免了可能同时发生的,转出转入造成的,根数据异常的情况
7.信号量(信号锁)
threading.BoundedSemaphore():信号量,可以限制每次执行的线程的个数
案例代码方法运用列表:
threading.BoundedSemaphore() #创建信号量对象,限制线程个数
.acquire() #创建信号量
.release() #释放信号量
代码案例:
import threading,time
class Mythread(threading.Thread):
def run(self):
if semaphore.acquire():
print(self.name)
time.sleep(2)
semaphore.release()
if __name__=="__main__":
semaphore=threading.BoundedSemaphore(3) #限制每次3个线程
threads=[]
for i in range(9):
threads.append(Mythread())
for t in threads:
t.start()
输出:
Thread-1
Thread-2
Thread-3 #每两秒打印三个线程名称
Thread-4
Thread-6
Thread-5
Thread-7
Thread-8
Thread-9
8.条件变量
代码案例方法运用列表:
threading.Condition() #创建条件变量对象
wait() #条件不满足时调用,线程会释放锁并进入等待状态
notify() #条件创造后调用,通知wait()所在语句激活一个线程
notifyAll() #条件创造后调用,通知wait()所在语句激活所有线程
import threading
from random import randint
import time
class robots(threading.Thread):
def run(self):
global save_work
while True:
num = randint(0,100)
print('机器人:',self.name,'举起了靶子:',str(num),save_work)
if con_lock.acquire():
save_work.append(num)
con_lock.notify()
con_lock.release()
time.sleep(3)
class shooter(threading.Thread):
def run(self):
global save_work
while True:
con_lock.acquire()
if not save_work:
con_lock.wait()
print('射手:', self.name, '打掉了' + str(save_work[0]) + '号靶子', save_work)
del save_work[0]
con_lock.release()
time.sleep(0.3)
if __name__ == '__main__':
save_work = []
con_lock = threading.Condition()
threads = []
for i in range(5):
threads.append(robots())
for i in range(2):
threads.append(shooter())
for t in threads:
t.start()
for t in threads:
t.join()
两个射手射击靶子,每3秒有5个机器人举起靶子。
9.事件发生
代码案例方法运用列表:
threading.Event() #创建事件对象
event.isSet() #返回event的状态值
event.wait() #如果 event.isSet()==False将堵塞线程
event.set() #设置event的状态值为True,所有weit()状态的线程激活
event.clear() #恢复event的状态值为False
代码案例:
import threading
import time
class Boss(threading.Thread):
def run(self):
print("老板:今天给请大伙聚餐!")
event.isSet() or event.set()
time.sleep(4)
print("聚餐结束......")
print('老板:刚好趁咱们兴头正盛,回去加班到十一点吧!')
event.isSet() or event.set()
class Worker(threading.Thread):
def run(self):
event.wait()
print("员工%s:year!!!"%(self.name))
time.sleep(0.25)
event.clear()
event.wait()
print("员工%s:No!!!"%(self.name))
if __name__=="__main__":
event=threading.Event()
threads=[]
for i in range(5):
threads.append(Worker())
threads.append(Boss())
for t in threads:
t.start()
for t in threads:
t.join()
输出:
老板:今天给请大伙聚餐!
员工Thread-4:year!!!
员工Thread-3:year!!!
员工Thread-1:year!!!
员工Thread-5:year!!!员工Thread-2:year!!!
聚餐结束......
老板:刚好趁咱们兴头正盛,回去加班到十一点吧!
员工Thread-2:No!!!员工Thread-4:No!!!
员工Thread-1:No!!!
员工Thread-5:No!!!
员工Thread-3:No!!!
Process finished with exit code 0
10.队列
队列:一种数据结构,特性是先进先出,先存入队列的数据,当需要取出数据时,先存入的先取出来
内置方法:
q.qsize() #返回队列的大小
q.empty() #如果队列为空,返回True,反之False
q.full() #如果队列满了,返回True,反之False
q.full 与 maxsize 大小对应
q.get([block[, timeout]]) #获取队列,timeout等待时间
q.get_nowait() #相当q.get(False)
非阻塞 q.put(item) #写入队列,timeout等待时间
q.put_nowait(item) #相当q.put(item, False)
q.task_done() #在完成一项工作之后,q.task_done() 函数向任务已经完成的队列发送一个信号
q.join() #实际上意味着等到队列为空,再执行别的操作
例子:
import threading
from random import randint
import time
import queue
class robots(threading.Thread):
def run(self):
global save_work
while True:
num = randint(0,100)
print('机器人:',self.name,'举起了靶子:',str(num))
save_work.put(num)
time.sleep(2)
class shooter(threading.Thread):
def run(self):
global save_work
while True:
print('射手:', self.name, '打掉了' + str(save_work.get()) + '号靶子')
time.sleep(0.3)
if __name__ == '__main__':
save_work = queue.Queue()
threads = []
for i in range(5):
threads.append(robots())
for i in range(2):
threads.append(shooter())
for t in threads:
t.start()