1.threading简介
threading库是python的线程模型,利用threading库我们可以轻松实现多线程任务。
2.进程与线程简介
通过上图,我们可以直观的总结出进程、线程及其之间的关系与特点:
- 进程是资源分配的最小单元,一个程序至少包含一个进程
- 线程是程序执行的最小单元,一个进程至少包含一个线程
- 每个进程都有自己独占的地址空间、内存、数据栈等;由于进程间的资源独立,所以进程间通信(IPC)是多进程的关键问题
- 同一进程下的所有线程都共享该进程的独占资源,由于线程间的资源共享,所有数据同步与互斥是多线程的难点
此外,多进程多线程也必须考虑硬件资源的架构:
- 单核CPU中每个进程中同时刻只能运行一个线程,只在多核CPU中才能够实现线程并发
- 当线程需运行但运行空间不足时,先运行高优先级线程,后运行低优先级线程
- Windows系统新开进行会增加很大的开销,因此Windows系统下使用多线程更具优势,这时更应该考虑的问题是资源同步和互斥
- Linux系统新开进程的开销增加很小,因此Linux系统下使用多进程更具优势,这时更应该考虑的问题是进程间通信
3.python多线程特点
python的多线程并非真正意义上的多线程,由于全局解释器锁(GIL)的存在,python中的线程只有在获得GIL后才拥有运行权限,而GIL同一时刻只能被一个线程所拥有。
python在运行计算密集型的多线程程序时,更倾向于让线程在整个时间片内始终占据GIL,而I/O密集型的多线程程序在I/O被调用前会释放GIL,允许其他线程在I/O执行时获得GIL,因此python的多线程更适用于I/O密集型的程序(网络爬虫)。
4.threading实现多线程
线程创建
守护线程是指在程序运行时在后台提供一种通用服务的线程,比如垃圾回收线程;这种线程在程序中并非不可或缺。
默认创建的线程为非守护线程,等所有的非守护线程结束后,程序也就终止了,同时杀死进程中的所有守护线程。
只要非守护线程还在运行,程序就不会终止。
import threading
import time#将创建的函数传递进threading.Thread()对象
def func():print(threading.current_thread().getName())
t1 = threading.Thread(target=func,name='test_threading_1')
t1.start()
t1.join()#继承threading.Thread类,改写run()方法
class TestThread(threading.Thread):def __init__(self,name):super(TestThread,self).__init__()self.name = namedef run(self):print(f'线程{self.name}正在进行!')n = 0while n < 5:n += 1print(f'线程{self.name}>>>{n}')time.sleep(1)print(f'线程{self.name}结束运行')
t1 = TestThread('thread-1')
t2 = TestThread('thread-2')
t1.start()
t2.start()
锁对象:threading.Lock
锁被用来实现对共享资源的同步访问;通过为每一个共享资源创建一个Lock对象,我们需要访问该资源时只能通过条用acquire方法来获取锁对象,待资源访问结束后,调用release方法释放Lock对象。
from threading import Thread,Lock
import timelock = Lock()def func():global n#加锁lock.acquire()team = ntime.sleep(1)n = team-1#释放锁lock.release()if __name__ == '__main__':n = 100l = []for i in range(100):t = Thread(target=func)l.append(t)t.start()for t in l:#将t线程设置成阻塞状态,直到t线程执行完后才能进入下一线程t.join()print(n)
from threading import Thread,Lockx = 0
lock = Lock()def func():global xlock.acquire()for i in range(6000):x = x+1lock.release()if __name__ == '__main__':t1 = Thread(target=func)t2 = Thread(target=func)t3 = Thread(target=func)t1.start()t2.start()t3.start()t1.join()t2.join()t3.join()print(x)
递归锁对象:threading.RLock
在应用锁对象时,会发生死锁;死锁是指两个或两个以上的线程在执行过程中,因争夺资源访问权而造成的互相等待的现象,从而一直僵持下去。
递归锁对象就是用来解决死锁问题的,RLock对象内部维护着一个Lock和一个counter变量,counter记录着acquire的次数,从而使得资源可以被多次acquire,直到一个线程的所有acquire都被release时,其他线程才能够acquire资源。
from threading import Thread,RLock,currentThread
import timerlock1 = rlock2 = RLock()class Test(Thread):def run(self):self.task1()self.task2()def task1(self):rlock1.acquire()print(f'{self.name}获得锁1')rlock2.acquire()print(f'{self.name}获得锁2')rlock2.release()print(f'{self.name}释放锁2')rlock1.release()print(f'{self.name}释放锁1')def task2(self):rlock2.acquire()print(f'{self.name}获得锁2')time.sleep(1)rlock1.acquire()print(f'{self.name}获得锁1')rlock1.release()print(f'{self.name}释放锁1')rlock2.release()print(f'{self.name}释放锁2')for i in range(3):t = Test()t.start()
条件变量对象:threading.Condition
import threading
import timecondition_lock = threading.Condition()PRE = 0def pre():print(PRE)return PREdef test_thread_hi():condition_lock.acquire()print('wait for the commend of test_thread_hello')condition_lock.wait_for(pre)print('contain doing')condition_lock.release()def test_thread_hello():time.sleep(1)condition_lock.acquire()global PREPRE = 1print('change PRE to 1')print('tell the test_thread_hi to acquire lock')condition_lock.notify()condition_lock.release()print('you need lock?')def main():thread_hi = threading.Thread(target=test_thread_hi)thread_hello = threading.Thread(target=test_thread_hello)thread_hi.start()thread_hello.start()if __name__ == '__main__':main()
信息量对象:threading.Semaphore
一个信号量管理一个内部计数器,acquire( )方法会减少计数器,release( )方法则增加计算器,计数器的值永远不会小于零,当调用acquire( )时,如果发现该计数器为零,则阻塞线程,直到调用release( ) 方法使计数器增加。
import threading
import timesemaphore4 = threading.Semaphore(4)def thread_semaphore(index):semaphore4.acquire()time.sleep(2)print('thread_%s is runing'%index)semaphore4.release()def main():for index in range(9):threading.Thread(target=thread_semaphore,args=(index,)).start()if __name__ == '__main__':main()
事件对象:threading.Event
如果一个或多个线程需要知道另一个线程的某个状态才能进入下一步的操作,就可以使用线程的event事件对象来处理。
import threading
import timeevent = threading.Event()def student_exam(student_id):print('学生%s等监考老师发卷'%student_id)event.wait()print('开始考试了')def invigilate_teacher():time.sleep(3)print('考试时间到了,学生们可以考试了')event.set()def main():for student_id in range(5):threading.Thread(target=student_exam,args=(student_id,)).start()threading.Thread(target=invigilate_teacher).start()if __name__ == '__main__':main()
定时器对象:threading.Timer
表示一个操作需要在等待一段时间之后执行,相当于一个定时器。