进程与线程
什么是线程:线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位,(线程是CPU运行的指令的集合)
什么是进程:是一块包含了某些资源的内存区域。操作系统利用进程把它的工作划分为一些功能单元。进程中所包含的一个或多个执行单元称为线程(进程是程序运行的资源集合,至少包含一个线程)。
进程与线程区别:
1.线程共享内存空间,进程的内存是独立的。
2.同一个进程的线程之间可以直接交流, 两个进程间通信,必须通过一个中间代理来实现
3.创建新线程很简单,创建新进程需要对父进程进行一次克隆(创建或撤消进程的开销明显大于创建或撤消线程的开销)。
4.一个线程可以控制和操作同一进程里的其它线程,但是进程只能操作子进程。
备注:在单核的CUP上同一时间CPU只会执行一个线程,表面上看到的同时执行,只是CPU执行时不停的再切换线程,但多核CUP是真正意义上的同时执行多个任务。
threading模块(用于提供线程相关的操作)
线程是应用程序中工作的最小单元,它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
实例1.多线程效果演示(直接调用):
import threading
import time
def run(msg, stop):
print(threading.current_thread(), msg) #当前线程是Thread,不是主线程
time.sleep(stop) #暂停时cpu会切换到其它线程去执行。
start_time = time.time()
jobs = [] #存主线程要等待哪些子线程执行完成才继续往下执行
for i in range(4):
t = threading.Thread(target=run, args=('t-%s' %i, 2)) #创建一个线程,args是传参数到run方法
#t.setDaemon(True) #把线程设置为守护线程(主线程结束,守护线程就结束了, 创建线程的线程为主线程)
t.start() #线程准备就绪,等待CPU调度
jobs.append(t)
print(threading.current_thread()) #打印当前线程,返回MainThread说明是主线程执行的。
print(threading.active_count()) #打印当前启动的线程数, 返回5 一个主线程,4个子线程
#等待jobs里的线程完成
# for t in jobs:
# t.join() #等待t线程执行完成
#print('执行时间', time.time() - start_time) #开起等待就打印2点几秒
#程序结束会等待所有的非守护线程执行完成才结束。
实例1.多线程效果演示(继承式调用)
import threading
import time
class MyThread(threading.Thread):
def __init__(self, num):
threading.Thread.__init__(self)
self.num = num
def run(self): # 定义每个线程要运行的函数
print("running on number:%s" % self.num)
time.sleep(2)
if __name__ == '__main__':
t1 = MyThread(1) #实例化一个线程
t2 = MyThread(2)
t1.start() ##线程准备就绪,等待CPU调度
t2.start()
thread方法:
t.start() : 激活线程
t.getName() : 获取线程的名称
t.setName() : 设置线程的名称
t.name : 获取或设置线程的名称
t.is_alive() : 判断线程是否为激活状态
t.isAlive() :判断线程是否为激活状态
t.setDaemon() 通过一个布尔值设置线程是否为守护线程,必须在执行start()方法之前才可以使用。如果是守护线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,均停止;如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程也执行完成后,程序停止
t.isDaemon() : 判断是否为守护线程
t.ident :获取线程的标识符。线程标识符是一个非零整数,只有在调用了start()方法之后该属性才有效,否则它只返回None
t.join() :主线程等待t线程执行完才往下执行。
t.run() :线程被cpu调度后自动执行线程对象的run方法(继承式多线程)
全局解释锁GIL:
作用:就是有它在,同一时刻,只能一个线程可以执行。所以Python的多线程,即使线程被均匀的打到不同的cpu上也只能有一个cpu在执行,完全没办法利用多核CPU,。GIL无疑就是一把全局排他锁。
单线程与多线程对比:
from threading import Thread
import time
def my_counter():
i = 0
for _ in range(100000000):
i = i + 1
return True
def main():
start_time = time.time()
my_counter() #执行两次
my_counter()
end_time = time.time()
print("Total time: {}".format(end_time - start_time)) #打印执行时间
def main2():
thread_array = {}
start_time = time.time()
for tid in range(2): #启两个线程,一个线程执行一次
t = Thread(target=my_counter)
t.start()
thread_array[tid] = t
for i in range(2): #等待两个线程执行完成
thread_array[i].join()
end_time = time.time()
print("thread Total time: {}".format(end_time - start_time)) #打印执行时间
if __name__ == '__main__':
main2() #调用多线程版
main() #调用单线程版
备注:打印出执行时间,差不多一样,多线程并没有比单线程快。
什么时候用多线程:如果是一个计算为主的程序(专业一点称为CPU密集型程序,以上例子就是),这一点确实是比较吃亏的,每个线程运行一遍,就相当于单线程再跑,甚至比单线程还要慢——CPU切换线程的上下文也是要有开销的。但是,如果是一个磁盘或网络为主的程序(IO密集型)就不同了。一个线程处在IO等待的时候,另一个线程还可以在CPU里面跑,有时候CPU闲着没事干,所有的线程都在等着IO,这时候他们就是同时的了,而单线程的话此时还是在一个一个等待的。我们都知道IO的速度比起CPU来是慢到令人发指的,python的多线程就在这时候发挥作用了。比方说多线程网络传输,多线程往不同的目录写文件,等等。
说明:首先需要明确的一点是GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。Python中同样一段代码可以通过CPython,PyPy,JPython等不同的Python执行环境来执行。像其中的JPython,pypy就没有GIL。然而因为CPython是大部分环境下默认的Python执行环境。所以在很多人的概念里CPython就是Python,也就想当然的把GIL归结为Python语言的缺陷。所以这里要先明确一点:GIL并不是Python的特性,Python完全可以不依赖于GIL
互斥锁(Lock与RLock)
lock = threading.Lock() #Lock对象
lock.acquire(): 锁定
lock.release(): 释放一个锁
lock.locked(): 判断是否得到锁
rLock = threading.RLock() #RLock对象
rlock.acquire(): 锁定
rlock.release(): 释放一个锁
rlock.locked(): 判断是否得到锁
这两种琐的主要区别是:RLock允许在同一线程中被多次acquire。而Lock却不允许这种情况。注意:如果使用RLock,那么acquire和release必须成对出现,即调用了n次acquire,必须调用n次的release才能真正释放所占用的琐。
何是用到锁:当多个线程要操作同一个资源时,为保证共享数据的完整性,加上锁就能确保锁定的内容在同一时间只有一个线程能执行。
锁实例:
import threading
import time
lock = threading.Lock() #创建锁对象
num = 0
def run():
lock.acquire() #锁定,使用Lock创建的锁不能在里面在嵌套锁了,如果要在里面嵌套锁就要用RLock
global num
num += 1 #所有的线程都要改这个资源,返有加锁能保证数据的正确性。虽然cpython解释器有全局解释锁,但是在不同的操作系统所表现出的效果可能不一样所有这里还是需要自己加锁(还有可以不用cpython呢!)。
time.sleep(2)
lock.release() #释放
jobs = []
for i in range(10):
t = threading.Thread(target=run)
t.start()
jobs.append(t)
for t in jobs:
t.join()
print(num)
备注:不加锁,运行结果可能会出现小于10的情况。
信号量(Semaphore)
互斥锁 同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据,比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去。
使用实例:
import threading, time
def run(n):
semaphore.acquire() #锁定的代码,同时允许多个线程执行。
time.sleep(1)
print("run the thread: %s\n" % n)
semaphore.release()
if __name__ == '__main__':
semaphore = threading.BoundedSemaphore(5) # 最多允许5个线程同时运行
for i in range(20):
t = threading.Thread(target=run, args=(i,))
t.start()
while threading.active_count() == 1:
print('----all threads done---')
Event(事件)
python线程的事件用于主线程控制其他线程的执行,事件主要提供了三个方法wait、clear、set
事件处理的机制:全局定义了一个“Flag”,如果“Flag”值为 False,那么当程序执行 event.wait 方法时就会阻塞,如果“Flag”值为True,那么event.wait 方法时便不再阻塞
clear:将“Flag”设置为False
set:将“Flag”设置为True
用threading.Event 实现线程间通信使用threading.Event可以使一个线程等待其他线程的通知,我们把这个Event传递到线程对象中,Event默认内置了一个标志,初始值为False。一旦该线程通过wait()方法进入等待状态,直到另一个线程调用该Event的set()方法将内置标志设置为True时,该Event会通知所有等待状态的线程恢复运行。
实例:
def worker(event):
while True:
print("子线程等待执行")
event.wait() #等待事件的信号标识被其它线程设置为True才往后执行
print("子线程执行完成")
time.sleep(1)
e = threading.Event() #主线程启动了一个线程事件, 默认信号标识为true
t = threading.Thread(target=worker, args=(e,))
t.start() #开始执行子线种
e.set() #将信号标识设置为true
time.sleep(2) #主线程暂停2秒后,可以将子线程信号标识设置为false,暂停子线程的执行。
e.clear() #将信号标识设置为false,此时子线程执行到event.wait()就会停止
print(e.is_set()) #判断信号标识是否为True
queue(队列)模块:
有如下三种常用队列:
queue.Queue(maxsize): 先进先出队列
queue.LifoQueue(maxsize): 后进先出队列
queue.PriorityQueue(maxsize): 优先级队列(可自定义优先级)
队列长度可为无限或者有限。可通过Queue的构造函数的可选参数maxsize来设定队列长度。如果maxsize小于1就表示队列长度无限。
队列的方法:
put(): 放数据,Queue.put()默认有block=True和timeout两个参数。当block=True时,写入是阻塞式的,阻塞时间由timeout确定。当队列q被(其他线程)写满后,这段代码就会阻塞,直至其他线程取走数据。Queue.put()方法加上 block=False 的参数,即可解决这个隐蔽的问题。但要注意,非阻塞方式写队列,当队列满时会抛出 exception Queue.Full 的异常
get(): 取数据(默认阻塞),Queue.get([block[, timeout]])获取队列,timeout等待时间
put_nowait(): 非阻塞写入队列, 相当put(item, False)
get_nowait(): 非阻塞取数据,相当get(False)
empty(): 如果队列为空,返回True,反之False
full(): 如果队列满了,返回True,反之False
qsize(): 显示队列中真实存在的元素长度
maxsize: 最大支持的队列长度,使用时无括号
join(): 阻塞等待队列中任务全部处理完毕,需要queue.task_done配合
task_done(): 主要是给join用的,每次get后需要调用task_done,直到所有任务都task_done。join才取消阻塞。
例子:
q=queue.Queue() #放包子的队列
def product(name): #做包子
count = 1
while True:
q.put(count) #放入队列中
print('%s做了%s个包子' %(name, count))
count += 1
time.sleep(1)
def consumer(name): #吃包子
while True:
baozi = q.get() #从队列里获取一个包子
print("%s吃了%s包子" %(name, baozi))
time.sleep(2) #第两秒吃一个包子
t=threading.Thread(target=product,args=('tanggl',))
t.start()
c=threading.Thread(target=consumer,args=('小狗1',))
c.start()
c=threading.Thread(target=consumer,args=('小狗2',))
c.start()
备注: 队列不线程安全的,小狗1,小狗2不可能同时吃了一个包子。