1 基本实现
Thread(target=函数名,args=(以元组形式传递的实参,要加","))
th = threading.Thread(target=run,args=(i,))就是实例化一个线程
th.start()就是对实例调用启动函数。
在内部,主线程就会创建30个子线程,子线程并发执行,所以虽然每个线程sleep(3),但是是并发执行的。
进程总耗时3s出头。
import time import threading def run(arg): print('run %s' %(arg)) time.sleep(3) for i in range(30): th = threading.Thread(target=run,args=(i,)) th.start()
2 深入理解线程
2.1 主线程和子线程
程序执行时,产生一个进程,进程里首先有一个主线程,每个执行的是子线程
使用threading.current_thread(),发现在for循环中是主线程,在run函数里是子线程。
import time import threading def run(arg): print('run %s' %(arg)) time.sleep(3) for i in range(3): th = threading.Thread(target=run,args=(i,)) print(threading.current_thread()) th.start() >>>> <_MainThread(MainThread, started 13112)> run 0 <_MainThread(MainThread, started 13112)> run 1 <_MainThread(MainThread, started 13112)> run 2
import time import threading def run(arg): print('run %s' %(arg)) print(threading.current_thread()) time.sleep(3) for i in range(3): th = threading.Thread(target=run,args=(i,)) th.start() >>>> run 0 <Thread(Thread-1, started 9976)> run 1 <Thread(Thread-2, started 9980)> run 2 <Thread(Thread-3, started 10800)>
2.2 子线程的并发性
cost_time的打印值:cost time 0.07500743865966797
发现没有在上面的for循环结束后再执行cost_time = time.time() - start_time语句。
因为每个实例化线程就是创建子线程,创建完后子线程自己去运行。主线程马上进入下一个执行语句,不会等待子线程执行完毕。
import time import threading def run(arg): print('run %s' %(arg)) time.sleep(10) start_time = time.time() for i in range(600): th = threading.Thread(target=run,args=(i,)) th.start() cost_time = time.time() - start_time print('cost time', (cost_time))
3 JOIN()
就是wait函数,一个线程调用join(),主线程就会等子线程执行完毕再往下走.
在这个程序中,相当于线程单并发顺序执行。
import time import threading def run(arg): print('run %s' %(arg)) time.sleep(1) start_time = time.time() for i in range(10): th = threading.Thread(target=run,args=(i,)) th.start() th.join() cost_time = time.time() - start_time print('cost time', (cost_time))
>>>
E:Python2018L1venvScriptspython.exe E:/Python/2018L1/test/threadingserver.py run 0 run 1 run 2 run 3 run 4 run 5 run 6 run 7 run 8 run 9 cost time 10.010000944137573
创建空列表t_list,然后把线程实例作为元素追加到列表(不在这里写join,不然会在每个线程中顺序sleep。)
在一个for循环里,让每个实例执行join()。
最后cost time 3.002300262451172
import time import threading def run(arg): print('run %s' %(arg)) time.sleep(3) start_time = time.time() t_list = [] for i in range(10): th = threading.Thread(target=run,args=(i,)) th.start() t_list.append(th) for i in t_list: i.join() cost_time = time.time() - start_time print('cost time', (cost_time))
4 setDaemon
当主线程退出,守护子线程不管有没有执行完毕,都会马上退出。
对于非守护子线程,主线程会等它执行完毕再退出。
对于守护线程,主线程不会等它执行完毕,而是直接退出。
4.1
以下程序,虽然没有join函数,但主线程也会在所有线程执行完毕后才退出。
import time import threading def run(arg): print('run %s' %(arg)) time.sleep(3) print(threading.current_thread()) for i in range(3): th = threading.Thread(target=run,args=(i,)) #th.setDaemon(True) th.start()
4.2
如果把子线程都设置为守护线程,就是说主线程不会在它执行完毕再退出,而是直接退出。
print(threading.current_thread())还没执行,就已经退出,不会再执行
import time import threading def run(arg): print('run %s' %(arg)) time.sleep(3) print(threading.current_thread()) for i in range(3): th = threading.Thread(target=run,args=(i,)) th.setDaemon(True) th.start()
4.3
主线程的执行时间比守护子线程长。在主线程退出前子线程已执行完毕。
import time import threading def run(arg): print('run %s' %(arg)) time.sleep(3) print(threading.current_thread()) for i in range(3): th = threading.Thread(target=run,args=(i,)) th.setDaemon(True) th.start() time.sleep(4)
5 GIL全局解释器锁
在cpython上,使用gil(global interpreter lock),来保证同一时间只有一个线程在解释器中运行。所有的线程并发都是通过context switch来完成。
这使得python无法真正使用多核cpu。
6 线程锁(互斥锁)
6.1 线程共享数据池
在进程中的多线程,共享进程的内存空间。就是可以对同一个数据进行操作
import time import threading num = 0 def run(): global num #函数使用全局变量,需要global声明 time.sleep(2) num += 1 print('thread:',num) for i in range(20): th = threading.Thread(target=run) th.start() time.sleep(5) print(num)
6.2 线程锁的作用
多个线程同时操作同一块数据,假设t1先拿到data = 0,然后使用解释器,调用cpu运算 data ++。还没运算完成,就被context switch。
这时t2也操作data,这是data = 0,然后使用解释器,调用cpu运算data ++,然后把data = 1写入data数据。
这时切换回t1,t1从context里拿到数据data = 0,运算完成,然后把data = 1写入data数据。
这样就出错了。
所以需要对data这块数据上锁,同一时间只能有一个线程进行操作。
python3上程序会自动加锁。
6.3 死锁和递归锁
https://blog.csdn.net/u013210620/article/details/78723704
用得着再看
6.4 信号量
同一时间有几个线程可以访问共享数据。
信号量就是多把锁,允许同时多个线程运行。
可以控制同一时刻可以运行的线程数。
7 Event事件
用于实现线程间通信
set表示设定标志位,clear是清空标志位,在处理标志位的函数体中
wait是等待标志位为set,is_set是判断标志位是否为set,在检测标志位的函数体中
import time import threading event = threading.Event() #-----红绿灯函数,在红灯时标志位清零,绿灯设置标志位----- def lighter(): count = 0 event.set() while True: count += 1 time.sleep(1) if count > 20 and count <= 30: event.clear() #20-30s,红灯 print('33[41mred light on33[0m') elif count > 30: event.set() count = 0 else: print('33[42mgreen light on33[0m') #----汽车函数,红灯停绿灯行,每秒1km---- def car(name): global mileage while True: if event.is_set(): mileage += 1 time.sleep(1) print('%s is running,the mileage is %s kilometer' % (name,mileage)) else: time.sleep(1) print('%s is wait' %(name)) event.wait() #等待标志位设为set if __name__ == '__main__': mileage = 0 car1 = threading.Thread(target=car,args=('本田车',)) car1.start() light = threading.Thread(target=lighter,) light.start()
8 应用场景
python是伪多线程,实际上是单线程在context switch,所以不适合cpu密集型操作任务,而适合io密集型任务。(socket的多并发网络连接就是io密集型任务)