什么是线程
线程是操作系统能够调度的最小的单位。它包含在进程之中,是进程中的实际运作单位。
一条进程中可以并发多个线程,每个线程执行不同的任务。
什么是进程
进程是系统进行资源分配和调度的最小单位,是指操作系统结构的基础。
调用线程的两种方式
#第一种方式
import threading
import time
def foo(num): # 定义每个线程要运行的函数
print("running on number:%s" % num)
time.sleep(3)
print('ok foo')
if __name__ == '__main__':#__name__ 是当前模块名,当模块被直接运行时模块名为 __main__ 。这句话的意思就是,当模块被直接运行时,以下代码块将被运行,当模块是被导入时,代码块不被运行。
t1 = threading.Thread(target=foo, args=(1,)) # 生成一个线程实例,传入了参数num为1
t2 = threading.Thread(target=foo, args=(2,)) # 生成另一个线程实例,传入了参数num为2
t1.start() # 启动线程
t2.start() # 启动另一个线程
print(t1.getName()) # 获取线程名
print(t2.getName())
继承式调用
#第二种方式
import time
import threading
class mythread(threading.Thread):
def __init__(self,num):
threading.Thread.__init__(self)#重写threading.Thread的__init__()方法
self.num = num
def run(self):
print('running on number %s'%self.num)#再此重写了threading.Thread的run方法
time.sleep(3)
print('ok run')
if __name__ == '__main__':
t1 = mythread(1)#第一步:创建一个线程对象t1
t2 = mythread(2)#第二部:创建一个线程对象t2
t1.start()#并发执行,开始执行线程t1
t2.start()#并发执行,开始执行线程t2
#运行结果
# running on number 1
# running on number 2
# ok run
# ok run
join和daemon
import threading
import time
from time import ctime,sleep
#定义函数听歌
def music(func):
for i in range(2):
print('开始听歌%s.%s'%(func,ctime()))
sleep(4)
print('结束听歌%s.%s'%(func,ctime()))
#定义函数看书
def watch(func):
for i in range(2):
print('开始读书%s.%s'%(func,ctime()))
sleep(5)
print('结束读书%s.%s'%(func,ctime()))
threads = []
t1 = threading.Thread(target=music,args=('无所谓',))
threads.append(t1)
t2 = threading.Thread(target=watch,args=('狂人日记',))
threads.append(t2)
if __name__ == '__main__':
for i in threads:
i.start()
i.join()
print('执行完成%s'%ctime())
#由于music和watch在for循环中并发执行
#第一步执行music 接下来 io阻塞4秒钟
# 开始听歌无所谓.Tue Apr 10 22:27:25 2018
#第一步执行读书 接下来Io阻塞5秒钟
# 开始读书狂人日记.Tue Apr 10 22:27:25 2018
#在第四秒的时候执行 听歌结束语句
# 结束听歌无所谓.Tue Apr 10 22:27:29 2018
#紧接着在第四秒和第五秒之间又开始了一个听歌的线程 接下来接着等待 4秒
# 开始听歌无所谓.Tue Apr 10 22:27:29 2018
#在第五秒的时候 结束看书狂人日记
# 结束读书狂人日记.Tue Apr 10 22:27:30 2018
#在第五秒的时候 立马又进来了一个线程开支执行 watch 开始看书 狂人日记 然后接着等待5秒
# 开始读书狂人日记.Tue Apr 10 22:27:30 2018
#在第八秒的时候结束听歌无所谓
# 结束听歌无所谓.Tue Apr 10 22:27:33 2018
#在第十秒的时候结束看书 狂人日记
# 结束读书狂人日记.Tue Apr 10 22:27:35 2018
#等待T2线程执行完了之后开始执行主线程
# 执行完成Tue Apr 10 22:27:35 2018
join的作用其实就是:在子线程完成运行之前,这个子线程的父线程将一直被阻塞。
import threading
import time
from time import ctime,sleep
#定义函数听歌
def music(func):
for i in range(2):
print('开始听歌%s.%s'%(func,ctime()))
sleep(4)
print('结束听歌%s.%s'%(func,ctime()))
#定义函数看书
def watch(func):
for i in range(2):
print('开始读书%s.%s'%(func,ctime()))
sleep(5)
print('结束读书%s.%s'%(func,ctime()))
threads = []
t1 = threading.Thread(target=music,args=('无所谓',))
threads.append(t1)
t2 = threading.Thread(target=watch,args=('狂人日记',))
threads.append(t2)
if __name__ == '__main__':
for i in threads:
i.setDaemon(True)
i.start()
#i.join()
print('执行完成%s'%ctime())
此时执行结果是:
开始听歌无所谓.Tue Apr 10 22:47:21 2018
开始读书狂人日记.Tue Apr 10 22:47:21 2018
执行完成Tue Apr 10 22:47:21 2018
setdaemon():守护线程,守护守护的意思就是我要保证在我守护范围内的线程执行完毕,so这个意思就是我守护这个线程执行完毕其他的就不管了,爱咋咋地管你有没有执行完,只要我守护的这个线程执行完了我就要关掉了。
将子线程设置为了守护线程。根据setDaemon()方法的含义,子线程打印内容后便结束了,不管父线程是否执行完毕了
程序运行中,执行一个主线程,如果主线程又创建一个子线程,主线程和子线程就分兵两路,分别运行,那么当主线程完成想退出时,会检验子线程是否完成。如果子线程未完成,则主线程会等待子线程完成后再退出。但是有时候我们需要的是,只要主线程完成了,不管子线程是否完成,都要和主线程一起退出,这时就可以用setDaemon方法了。
Python中万恶的GIL
在Python中其实并不能实现真正的并行执行,而是多个线程抢占cpu,只能说是并发执行而非并行执行。在Python中同一时刻只能有一个线程使用cpu,但是在Java和Csharp中就是可以实现真正的并行执行的。
用一个例子来看GIL
import threading
#将100减到1,串行的时候是执行一百次,最后执行到1.现在用直接开始一百个线程每次执行减一
def reduce():
global num
temp = num
#print('ok')
temp -= 1
num = temp
num = 100
threads = []
for i in range(100):
t = threading.Thread(target=reduce)
t.start()
threads.append(t)
for i in threads:
i.join()
在计算减法的运算中如果没有print('ok')这个io阻塞结果一直是0
当加上print('ok')这个io阻塞的时候
这个也就说明了在Python执行计算密集型函数的时候及时开再多的线程也是不起作用的,究其根本原因就是有GIL管着,在进行计算密集型函数的时候他的切换线程的速度,大于计算的速度的时候就会发生线程间值覆盖的问题。但是怎么解决这个问题呢,这个时候就需要一把锁把进程来锁住。
同步锁lock
#lock
import threading
#将100减到1,串行的时候是执行一百次,最后执行到1.现在用直接开始一百个线程每次执行减一
def reduce():
global num
r.acquire()#执行锁住的操作
temp = num
print('ok')
temp -= 1
num = temp
r.release()#执行解锁的操作
num = 100
threads = []
r = threading.Lock()#创建了一把进程锁
for i in range(100):
t = threading.Thread(target=reduce)
t.start()
threads.append(t)
for i in threads:
i.join()
print('all over',num)
此时加了进程锁lock之后就解决了此类问题,在单单的计算的这一步骤还是相当于串行执行的。
死锁
import threading,time
class myThread(threading.Thread):
def doA(self):
lockA.acquire()
print(self.name,"gotlockA",time.ctime())
time.sleep(3)
lockB.acquire()
print(self.name,"gotlockB",time.ctime())
lockB.release()
lockA.release()
def doB(self):
lockB.acquire()
print(self.name,"gotlockB",time.ctime())
time.sleep(2)
lockA.acquire()
print(self.name,"gotlockA",time.ctime())
lockA.release()
lockB.release()
def run(self):
self.doA()
self.doB()
if __name__=="__main__":
lockA=threading.Lock()
lockB=threading.Lock()
threads=[]
for i in range(5):
threads.append(myThread())
for t in threads:
t.start()
for t in threads:
t.join()
此时就会发生死锁的情况,此时的解决办法就是加递归锁
递归锁
就是创建锁的时候使用threading.Rlock()此时使用Rlock()创建的就是一个递归锁,Rlock可以多次执行acquire和release每次执行就像在数学表达式里加了小括号一样,在小括号中加小括号(没有并列每个括号中只有一个括号),每次解锁就需要先解锁最内层的然后一层层往外执行。
条件变量同步condition
有时候在实际操作中有些线程需要满足一定条件之后再去执行。threading.condition提供了条件变量线程支持。
condition除了能有Rlock和lock方法之外,还提供了wait(),notify(),notifyAll(),方法
语法:lock_condition = threading.Condition([Lock/Rlock])
wait():线程释放锁并且进入阻塞状态。
notify():条件创造后调用,通知等待线程池激活一个线程.
notifyAll():条件创造后调用通知线程池,激活所有线程.
#应用场景:建立一个做包子的线程类,一个吃包子的线程类
import threading,time
from random import randint
class zuobaozi(threading.Thread):
def run(self):
global L
while True:
val = randint(1,100)
print('做包子',self.name,'append:',str(val),L)
if lock_con.acquire():
L.append(val)
lock_con.notify()
lock_con.release()
time.sleep(3)
class chibaozi(threading.Thread):
def run(self):
global L
while True:
lock_con.acquire()
if len(L) == 0:
lock_con.wait()
print('吃包子',self.name,'delete:',str(L[0]),L)
del L[0]
lock_con.release
time.sleep(1)
if __name__ == '__main__':
L = []
lock_con = threading.Condition()
threads = []
for i in range(5):
threads.append(zuobaozi())
threads.append(chibaozi())
for t in threads:
t.start()
for i in threads:
i.join()
同步条件 Event
event其实和condition是差不多的只不过没有了condition的锁的功能.设计于不访问共享资源的条件环境.event = threading.Event(bool)
isSet():返回event的状态值
event.wait():如果状态值是false则阻塞
enent.set():设置状态值为True,将阻塞线程池的线程激活进入就绪状态.
event.clear():恢复状态值为false
import threading,time
class Boss(threading.Thread):
def run(self):
print("BOSS:今晚大家都要加班到22:00。")
event.isSet() or event.set()
time.sleep(5)
print("BOSS:<22:00>可以下班了。")
event.isSet() or event.set()
class Worker(threading.Thread):
def run(self):
event.wait()
print("Worker:哎……命苦啊!")
time.sleep(0.25)
event.clear()
event.wait()
print("Worker:OhYeah!")
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()
BOSS:今晚大家都要加班到22:00。
Worker:哎……命苦啊!
Worker:哎……命苦啊!
Worker:哎……命苦啊!
Worker:哎……命苦啊!
Worker:哎……命苦啊!
BOSS:<22:00>可以下班了。
Worker:OhYeah!
Worker:OhYeah!
Worker:OhYeah!
Worker:OhYeah!
Worker:OhYeah!
信号量Semaphore
信号量用来控制线程并发数的,BoundedSemaphore或Semaphore管理一个内置的计数 器,每当调用acquire()时-1,调用release()时+1。
计数器不能小于0,当计数器为 0时,acquire()将阻塞线程至同步锁定状态,直到其他线程调用release()。(类似于停车位的概念)
BoundedSemaphore与Semaphore的唯一区别在于前者将在调用release()时检查计数 器的值是否超过了计数器的初始值,如果超过了将抛出一个异常
import threading
import time
class MyThread(threading.Thread):
def run(self):
if semphores.acquire():
print(self.name)
time.sleep(5)
semphores.release()
if __name__ == '__main__':
semphores = threading.Semaphore(5)
threads = []
for i in range(100):
threads.append(MyThread())
for t in threads:
t.start()
多线程利器queue
queue就是一种数据类型,这种数据类型的特点就是先进先出,FIFO,是一种堆栈的结构,创建queue的时候需要指定队列的长度,当队列中的元素个数达到制定的个数的时候,接下来的就会进入等待,知道空出一个位置出来
常用方法
增:queue.put()
查:queue.get()
队列大小:queue.qsize()
判断是否是空:queue.empty()
判断队列是否满了:queue.full()
等待对列为空然后执行别的操作:queue.join()
import threading,queue
from time import sleep
from random import randint
class Production(threading.Thread):
def run(self):
while True:
r=randint(0,100)
q.put(r)
print("生产出来%s号包子"%r)
sleep(1)
class Proces(threading.Thread):
def run(self):
while True:
re=q.get()
print("吃掉%s号包子"%re)
if __name__=="__main__":
q=queue.Queue(10)
threads=[Production(),Production(),Production(),Proces()]
for t in threads:
t.start()
进程:
进程和线程的用法基本相同,就不巴拉巴拉说一大堆了.在进程中没有GIL所以使用进程可以实现真正的并发执行.
进程的两种调用方式:
第一种方式:
from multiprocessing import Process
import time
def foo(name):
time.sleep(1)
print('hello',name,time.ctime())
if __name__ == '__main__':
p_list = []
for i in range(5):
p = Process(target=foo,args=('sign',))
p_list.append(p)
p.start()
for t in p_list:
t.join()
print('end')
hello sign Sat Apr 14 10:53:09 2018
hello sign Sat Apr 14 10:53:09 2018
hello sign Sat Apr 14 10:53:09 2018
hello sign Sat Apr 14 10:53:09 2018
hello sign Sat Apr 14 10:53:09 2018
end
第二种方式
from multiprocessing import Process
import time
class myprocess(Process):
def __init__(self):
super(myprocess,self).__init__()
def run(self):
time.sleep(1)
print('hello',self.name,time.ctime())
if __name__ == '__main__':
p_list = []
for i in range(5):
p = myprocess()
p.start()
p_list.append(p)
for t in p_list:
t.join()
print('end')
hello myprocess-1 Sat Apr 14 11:04:03 2018
hello myprocess-2 Sat Apr 14 11:04:03 2018
hello myprocess-4 Sat Apr 14 11:04:03 2018
hello myprocess-3 Sat Apr 14 11:04:03 2018
hello myprocess-5 Sat Apr 14 11:04:03 2018
end
Process类
构造方法:
Process([group [, target [, name [, args [, kwargs]]]]])
target:要执行的方法名
name:进程名
args:参数
实例方法:
is_alive():判断进程是否还在运行
join():与线程的join作用相同
run():start()调用run()方法
start():进入准备cpu开始调用
terminate():不管任务是否完成理解结束任务.
属性:
authkey
daemon:和线程的setDeamon功能一样
exitcode(进程在运行时为None、如果为–N,表示被信号N结束)
name:进程名字。
pid:进程号。
def foo(i):
global p
time.sleep(1)
print (p.is_alive(),i,p.pid)
time.sleep(1)
if __name__ == '__main__':
p_list=[]
for i in range(10):
p = Process(target=foo, args=(i,))
p.daemon=True
p_list.append(p)
for p in p_list:
p.start()
for p in p_list:
p.join()
进程间的通信
使用Queues
使用方法类似于threading里的queue,但是含义却是大不相同
from multiprocessing import Process, Queue
def f(q,n):
q.put([42, n, 'hello'])
if __name__ == '__main__':
q = Queue()
p_list=[]
for i in range(3):
p = Process(target=f, args=(q,i))
p_list.append(p)
p.start()
print(q.get())
print(q.get())
print(q.get())
for i in p_list:
i.join()
使用pipes进行进程间通信
#使用pipes进行进程间通信
from multiprocessing import Process,Pipe
import time
def f(conn):
conn.send('约吗?')
print(conn.recv())
conn.close()
if __name__ == '__main__':
parent_conn,children_conn = Pipe()
p = Process(target=f,args=(children_conn,))
p.start()
print(parent_conn.recv())
parent_conn.send('滚!!!')
p.join()
使用pipe进行线程间通讯更类似于socket的通信.
协程:
协程只有一个线程,省去了上下文切换的消耗,不需要锁了再,
高并发+高扩展性+低成本:一个cpu支持上完的协程都不是问题,所以很适合高并发处理
但是,无法利用多核资源,和进行阻塞的时候