线程与进程之间的关系:一个程序至少有一个进程,一个进程至少有一个线程
进程见上一篇博文,其实线程和进程的用法差不多,但是由于GIL(全局解释器锁)的存在导致时时刻刻只能由一个线程在执行,并不能真正意义上实现多线程
1、线程
在一个进程的内部要同时干多件事那么就需要运行多个子任务那么这些子任务叫做线程
线程通常叫做轻型的进程,线程是共享内存空间的并发执行的多任务每一个线程都共享一个进程的资源
线程是最小的执行单元,而进程至少有一个线程组成,如何调度进程和线程,完全由操作系统决定,程序自己不能决定何时执行执行多长时间
线程模块
1、_thread模块: 低级模块
2、threading模块 高级模块,对_thread进行了封装
线程的实现
import threading,time
a = 10
def run(num):
print("子线程%s开始" % (threading.current_thread().name))
time.sleep(2)
print("打印",num)
time.sleep(2)
print(a)
print("子线程%s结束" % (threading.current_thread().name))
if __name__ == "__main__":
#任何进程都会默认启动一个线程这个线程成为主线程,主线程可以启动新的子线程
#current_thread():返回当前线程的实例
print("主线程%s启动" % (threading.current_thread().name))
#创建子线程
t = threading.Thread(target=run,name = "runThread",args=(1,))
t.start()
#等待线程结束
t.join()
print("主线程%s结束" % (threading.current_thread().name))
线程间共享数据
import threading
num = 10
def run(n):
global num
for i in range(100000000):
num = num + n
num = num - n
if __name__ == "__main__":
t1 = threading.Thread(target=run,args=(6,))
t2 = threading.Thread(target=run,args=(9,))
t1.start()
t2.start()
t1.join()
t2.join()
print("num = " + str(num)
线程间解决数据混乱
import threading
#锁对象
lock = threading.Lock()
num = 10
def run(n):
global num
for i in range(1000000):
#锁,确保代码只能由一个线程从头到尾执行,阻止了多线程的并发执行
#包含锁的某段代码实际上只能以单线程模式执行所以效率大大的降低了
#由于可以存在多个锁,不同的线程持有不同的锁,并试图获取其他的锁可能造成思索,导致多个线程挂起,只能靠操作系统强制终止
lock.acquire()
try:
num = num + n
num = num - n
finally:
#修改完一定要释放锁
lock.release()
#与上面代码功能相同,with lock可以自动上锁与解锁
with lock:
num = num + n
num = num - n
if __name__ == "__main__":
t1 = threading.Thread(target=run,args=(6,))
t2 = threading.Thread(target=run,args=(9,))
t1.start()
t2.start()
t1.join()
t2.join()
print("num = " + str(num))
ThreadLocal
创建一个全局的ThreadLocal对象
每个线程有独立的存储空间
每个线程对ThraedLocal对象都可以读写,但是互不影响
作用:可以为每一个线程绑定一个数据库链接,或者HTTP请求,或者用户身份信息等
这样一个线程的所有调用到的处理函数都可以非常方便的访问这些资源
import threading
num = 0
local = threading.local()
def run(x,n):
x = x + n
x = x - n
def func(n):
#每个线程都有一个local.x就是线程的局部变量
local.x = num
for i in range(1000000):
run(local.x,n)
print("%s-%d" % (threading.current_thread().name,local.x))
if __name__ == "__main__":
t1 = threading.Thread(target=func,args=(6,))
t2 = threading.Thread(target=func,args=(9,))
t1.start()
t2.start()
t1.join()
t2.join()
print("num ",num)
2、协程
协程,又称微线程,纤程。英文名Coroutine。协程是一种用户态的轻量级线程。
协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:
协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。
线程的切换,会保存到CPU的寄存器里。
CPU感觉不到协程的存在,协程是用户自己控制的。
之前通过yield做的生产者消费者模型,就是协程,在单线程下实现并发效果。
协程的好处:
无需线程上下文切换的开销
无需数据操作锁定及同步的开销
方便切换控制流,简化编程模型
高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。
缺点:
无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上
进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序
使用yield实现协程操作实例
import time
def consumer(name):
print("--->starting eating baozi...")
while True:
new_baozi = yield
print("[%s] is eating baozi %s" % (name,new_baozi))
# time.sleep(1)
def producer():
r = con.__next__()
r = con2.__next__()
n = 0
while n < 5:
n +=1
print("\033[32;1m[producer]\033[0m is making baozi %s" %n )
con.send(n)
con2.send(n)
time.sleep(1)
if __name__ == '__main__':
con = consumer("c1") # 第一次调用只是生成器,next的时候才回生成
con2 = consumer("c2")
p = producer()