在Python中使用threading模块提供多线程功能
#这里有2中调用方式一种是直接调用一直是方法重写
#直接调用
import threading
import time
def say1(*args):
print('hello {}'.format(args[0]))
time.sleep(3)
def say2(*args):
print('hello {}'.format(args[0])
if __name__ = "__main__":
t1 = threading.Thread(target=say1,args=('1',)) #建立一个对象
t2 = threading.Thread(target=say2,args=('2',)) #建立一个对象
t1.start() #启动线程
t2.start() #启动线程
print(t1.getName())#打印线程名
print(t2.getName))
上面的那种方式就实现了两个函数并行执行,但是有一个问题,如果我们如果想在这两个进程执行完毕后在执行后续的代码会怎么样了,
import threading
import time
def say1(*args):
print('hello {}'.format(args[0]))
time.sleep(3)
def say2(*args):
print('hello {}'.format(args[0])
if __name__ = "__main__":
t1 = threading.Thread(target=say1,args=('1',))
t2 = threading.Thread(target=say2,args=('2',))
t1.start()
t2.start()
print('start run last ....')
#执行结果
nihao 1
ni hao 22
Thread-1
Thread-2
start run last ....
#这里下面那一句并没有在两个线程执行完成后执行,因为调用线程的程序本身就是一个线程,父线程调用完子线程之后便和两个子线程没有任何关系了这里就需要用到join方法,手动的去阻塞这个父线程,只有子线程执行完毕,才能接着往下执行
import threading
import time
def say1(*args):
print('hello {}'.format(args[0]))
time.sleep(3)
def say2(*args):
print('hello {}'.format(args[0])
if __name__ = "__main__":
t1 = threading.Thread(target=say1,args=('1',))
t2 = threading.Thread(target=say2,args=('2',))
t1.start()
t2.start()
t1.join()
t2.join()
print('start run last ....')
#这样只有t1,t2两个线程执行完毕才会执行下面的那句代码
批量创建线程
import threading
import time
def say1(*args):
print('hello {}'.format(args[0]))
time.sleep(3)
if __name__ = "__main__":
threadList = []
for i in range(1,10)
t = threading.Thread(target=say1,args=(i,))
t.start()
threadList.append(t)
for i in threadList:
i.join()
守护线程
守护线程的含义是当一个线程被设置为子线程的守护线程时候,当主线程停止,那么子线程无论是否执行完毕都将被kill掉,通过setDaemon(True)来设置
import threading
import time
def say1(*args):
print('ni hao {}'.format(args[0])
time.sleep(3)
print('done')
def main():
for i in range(5):
t1 = threading.Thread(target=say1,args=[i,])
t1.start()
print('start thread :' t1.getName())
if __name__ == '__main__':
t = threading.Thread(target=main,args=[])
t.start()
t.setDaemon(True)#如果t.setDaemon(Flase)代表不设置守护线程,设置成守护线程后主线程执行完毕,子线程全部Kill掉。
GIL锁(互斥锁) Lock方法
下面看一段代码,如果我启动了100个线程,同时对一份数据进行修改会有什么结果呢?
import threading
import sleep
def run():
global number
print('get num:' , number)
time.sleep(1)
number -= 1
number = 100
thread_list = []
for i in range(100):
t = threading.Thread(target=run)
t.start()
thread_list.append(t)
for i in thread_list:
t.join()
print('last number:' number
这段代码通过原生解释器执行会发现每次执行的结果不一样,因为官方解释中。原生Cpython解释器是通过C接口调用,Python可以开启多个线程,但是每次由于引用了GIL的概念,每次只能运行一个线程,在解释器层面上,通过计时器对每个线程进行切换,尽可能的达到并行运行的目的,上面那段代码中,number =100 在内存中改内存保存为100,这个时候线程1取得这个值并操作得到100-1=99,这个时候如果这个值还在线程中,没有写入内存,就发生了切换到线程2,线程2取的值也是100,操作得到99,没有发生切换,写到了内存,如果再切换回到线程1,接着保存到99,这样线程2所做的操作就会被线程1刷掉,这样就能解释到为什么每次运行的结果都不一样,但是GIL是为了避免Python多线程启动多个原生C线程同时对数据进行修改,这个时候就需要用到Lock方法,为数据加锁.
import threading
import sleep
def run():
global number
print('get num: ',number)
sleep(1)
lock.acquire()#获取一把锁
number -= 1
local.release()#释放这把锁
locak = threading.Lock()
number = 100
thread_list = []
for i in range(100):
t = threading.Thread(target=run)
t.start()
thread_list.append(t)
for i in thread_list:
i.join()
不过记住如果加了一把锁,这样在你没有释放这把锁之前,所有的线程都只能等待,多线程就变成单线程了,所以尽可能的将快速执行的代码放在锁里面,提高运行效率。
线程间状态传递
大家知道线程之间,内存是共享的,这里有一段模拟吃饭的代码来解释线程之间状态的传递
import threading,random,time
#这段代码是表示饭店已经做好了饭
def fd():
if not event.isSet():
print('饭已经做好了,请吃饭...')
event.set()#改变状态Event=True
def gk(number):
time.sleep(2)
if event.isSet():
print('顾客[%s]开始吃饭..' % number)
if __name__ == '__main__':
event = threading.Event()
t = threading.Thread(target=fd)
for i in range(10):
m = threading.Thread(target=gk,args=[i,])
m.start()
这段代码的意思是,首先创建一个线程,启动饭店,饭店做好饭,将Event=True
接着开启了10个gk线程,gk线程接收到Event=True的状态,开始吃饭。