线程
一、线程的理解
开启一个word,就是一个进程,
但是可以在这个进程里实现多任务。
通俗的将:开启一个word应用程序,可以在任务管理器查看到它的进程ID,但是
在word里面的其他功能,比如:复制,打印等功能,系统在调用这些功能的时候,并没有
开启新的进程,那他们是怎么执行的呢?答案就是 线程
在一个进程的内部,要同时干多件事,就需要同时运行多个'子任务',我们
把进程内的这些'子任务'叫做线程
线程通常叫做轻型的进程,线程是共享内存空间的并发执行的多任务,
每一个线程都共享一个进程的资源。
线程是没有独立的堆栈的,他们共享的是进程的资源
线程是最小的执行单元,而进程由至少一个线程组成,如何
调度进程和线程,完全由操作系统决定,程序自己不能决定什么时候
执行,执行多长时间。
模块
1、_thread模块 低级模块(所谓低级,是指它接近底层,用C语言写的,不是功能低级)
2、threading模块 高级模块, 对_thread进行了封装
二、启动一个线程
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 import threading 5 import time 6 7 a = 10 8 9 10 def run(num): 11 print('子线程(%s)开始' % (threading.current_thread().name)) 12 13 # 实现线程的功能 14 print('打印') 15 time.sleep(2) 16 17 print('子线程(%s)结束' % (threading.current_thread().name)) 18 time.sleep(2) 19 print(a, num) 20 21 22 if __name__ == '__main__': 23 # 任何进程默认就会启动一个线程,这个线程成为主线程, 24 # 主线程可以启动新的子线程(他和进程一样) 25 26 # current_thread():返回当前线程的实例 27 print('主线程(%s)启动' % (threading.current_thread().name)) #打印线程的名称 28 29 # 创建子进程 30 # name: 线程的名称 31 t = threading.Thread(target=run, name='runThread', args=(1,)) 32 t.start() 33 34 # 等待线程结束 35 t.join() 36 37 print('主线程(%s)结束' % (threading.current_thread().name))
三、线程间共享数据
多线程和多进程之间最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在每一个进程当中,
互不影响,(进程间不共享全局变量),
而多线程之间,所有变量都由所有线程共享。所以,任何一个变量都可以被任意一个线程修改,因此,
线程之间,共享数据最大的危险在于多个线程同时修改一个变量,容易把内容该乱了。
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 import threading 5 6 NUM = 100 7 8 def run1(n): 9 global NUM 10 for i in range(10000000): 11 NUM = NUM + n 12 NUM = NUM - n 13 print(threading.current_thread().name, NUM) 14 15 def run2(n): 16 global NUM 17 for i in range(10000000): 18 NUM = NUM + n 19 NUM = NUM - n 20 21 print(threading.current_thread().name, NUM) 22 23 24 if __name__ == '__main__': 25 t1 = threading.Thread(target=run1, args=(6,)) 26 27 t2 = threading.Thread(target=run2, args=(9,)) 28 29 t1.start() 30 t2.start() 31 t1.join() 32 t2.join() 33 34 print('NUM = ' + str(NUM))
四、使用线程锁解决数据混乱
由于线程之间存在数据共享,容易出现错误数据的问题。看一下例子:
两个线程同时工作,一个存钱,一个取钱
100 两个任务:1、先取出20
2、再往里面存10
线程一开启:
A B C
操作一:100 - 20 = 80
操作二:A = C # C赋值给A CPU被抢占,操作二等待中
此时,线程二抢占到CPU,执行操作三
线程二开启:
A D E
操作三:100 + 10 = 110
操作四:A = E # 线程二完成赋值操作 此时A=110
线程二执行完毕,线程一才抢到CPU,继续执行未执行的操作二,(赋值操作)
操作二:A = C # 此时,A = 80
这样出现的数据的混乱,少了10元。也可能出现110元。
解决方法:加锁!!!
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 4 import threading 5 6 # 锁对象 7 lock1 = threading.Lock() 8 NUM = 100 9 10 11 def run1(n): 12 global NUM 13 for i in range(1000000): 14 # 在这里加一把锁 15 # 确保了这段代码只能由一个线程从头到尾的完整执行 16 # 阻止了多线程的并发执行,包含锁的某段代码实际上只能 17 # 以单线程模式执行,所以效率大大滴降低了 18 19 # 由于它可以存在多个锁,不同线程可以持有不同的锁,并试图获取其他的锁,这样 20 # 锁来锁去的,可能造成死锁,导致多个线程挂起。只能靠操作系统强制终止。 21 # 解决方法,使用 try 和 finally 22 lock1.acquire() 23 try: 24 NUM = NUM + n 25 NUM = NUM - n 26 finally: 27 # 修改完一定要释放锁 28 lock1.release() 29 # print(threading.current_thread().name, NUM) 30 31 ''' 32 与上面的代码功能相同,with lock可以自动上锁和解锁 33 with lock: 34 num = num + n 35 num = num - n 36 ''' 37 38 39 if __name__ == '__main__': 40 t1 = threading.Thread(target=run1, args=(6,)) 41 t2 = threading.Thread(target=run1, args=(9,)) 42 t1.start() 43 t2.start() 44 t1.join() 45 t2.join() 46 47 print('NUM = ' + str(NUM))
五、ThreadLocal
关于多线程的数据混乱,除了加锁,还可以使用 ThreadLocal
创建一个全局的ThreadLocal对象
每个线程有独立的存储空间
每个线程对自己线程内部 ThreadLocal 对象都可以读写,但是与同一进程下的其他线程的ThreadLocal变量互不影响
1 import threading 2 3 NUM = 0 4 5 # 创建一个全局的ThreadLocal对象 6 # 每个线程有独立的存储空间 7 # 每个线程对自己线程内部 ThreadLocal 对象都可以读写,但是与同一进程下的其他线程的ThreadLocal变量互不影响 8 local = threading.local() 9 10 11 def run(x, n): 12 x = x + n 13 x = x - n 14 15 def func(n): 16 # 每个线程走到这里都会创建一个loacl.x属性 它就是线程的局部变量 17 # 给线程里增加一个x属性。 18 # 把全局变量赋值给一个局部变量。 19 ''' 20 也就是说,每一个线程在执行local.x属性值的计算的时候,它在自己的线程里都可以找到对应的 21 loacl.x 所以它就不会在往外层代码寻找local.x来做计算。这样就不防止线程在自己线程内部找不到 22 local.x 的情况下,往外层程序寻找变量,导致数据混乱。 23 24 ThreadLocal 作用: 让线程有一个自己的空间,存储一些自己的局部变量。这些局部变量使我们 25 线程自己独有的 26 ''' 27 28 local.x = NUM 29 for i in range(1000000): 30 run(local.x, n) 31 print('%s-%d' % (threading.current_thread().name, local.x)) 32 33 34 if __name__ == '__main__': 35 t1 = threading.Thread(target=func, args=(6,)) 36 t2 = threading.Thread(target=func, args=(9,)) 37 t1.start() 38 t2.start() 39 t1.join() 40 t2.join() 41 print('NUM = ', NUM) 42 43 # 作用:为每一个线程绑定一个数据库连接,或者是HTTP请求,或者用户信息等, 44 # 这样一个线程的所有调用到的处理函数都可以非常方便的访问着些资源。
六、信号量控制线程数量
threading.Semaphore(int)
控制一定线程数量一起执行,当数量不足时,剩下的一起执行。
1 import threading 2 3 import time 4 5 # semaphore: 臂板信号。 6 # 让3个线程一起执行 7 sem = threading.Semaphore(3) 8 9 10 def run(): 11 # 让3个线程一起执行,当线程数量不足3个时,剩下的一起执行。 12 with sem: 13 for i in range(5): 14 print('%s--%d' % (threading.current_thread().name, i)) 15 time.sleep(1) 16 17 18 if __name__ == '__main__': 19 20 # 开启5个线程 21 for i in range(5): 22 threading.Thread(target=run).start()
七、凑够一定数量,才能一起执行
threading.Barrier(parties)
数量如果没有凑够,就一直等待……
1 import threading 2 import time 3 4 # 凑数量 5 bar = threading.Barrier(4) 6 7 8 def run(): 9 print('%s---start----%d' % (threading.current_thread().name, i)) 10 time.sleep(1) 11 # 凑够一定数量,才会执行,凑不够就会等待 12 bar.wait() 13 print('%s----end----%d' % (threading.current_thread().name, i)) 14 15 16 if __name__ == '__main__': 17 # 开启5个线程 18 for i in range(5): 19 threading.Thread(target=run).start()
八、定时线程
threading.Timer(interval, function)
1 import threading 2 3 4 def run(): 5 print('sunck is a good man') 6 7 8 # 线程5秒钟后执行run方法 9 t = threading.Timer(5, run) 10 t.start() 11 12 t.join() # 让父线程等待子线程执行结束,再结束父进程。 13 print('父线程结束')
九、线程通信
threading.Event()
1 import threading 2 import time 3 4 5 def func(): 6 # 线程之间的通信,可以用事件去实现 7 # 创建一个事件对象 8 event = threading.Event() 9 10 def run(): 11 for i in range(5): 12 # wait:阻塞,等待事件的触发 13 event.wait() 14 # clear(): 重置,使上面的wait重置到阻塞状态。 15 event.clear() 16 print('sunck is a good man! %d' % i) 17 18 t = threading.Thread(target=run).start() 19 return event 20 21 22 e = func() 23 # 触发事件 24 for i in range(5): 25 time.sleep(2) 26 e.set()
十、生产者与消费者
1 import threading 2 import queue 3 import time 4 import random 5 6 7 # 生产者 8 def product(tid, q): 9 while True: 10 num = random.randint(0, 10000) 11 q.put(num) 12 print('生产者%d生产了%d数据放入了队列' % (tid, num)) 13 time.sleep(3) 14 # 任务完成 15 q.task_done() 16 17 18 # 消费者 19 def customer(tid, q): 20 while True: 21 item = q.get() 22 if item is None: 23 break 24 print('消费者%d消费了%d数据' % (tid, item)) 25 time.sleep(2) 26 # 任务完成 27 q.task_done() 28 29 30 if __name__ == '__main__': 31 # 消息队列 32 q = queue.Queue() 33 # 启动生产者 34 for i in range(4): 35 threading.Thread(target=product, args=(i, q)).start() 36 # 启动消费者 37 threading.Thread(target=customer, args=(i, q)).start()
十一、线程调度
1 import threading 2 import time 3 4 5 # 线程条件变量 6 cond = threading.Condition() 7 8 9 def run1(): 10 with cond: 11 for i in range(0, 10, 2): 12 print(threading.current_thread().name, i) 13 time.sleep(1) 14 cond.wait() 15 cond.notify() 16 17 18 def run2(): 19 with cond: 20 for i in range(1, 10, 2): 21 print(threading.current_thread().name, i) 22 time.sleep(1) 23 cond.notify() 24 cond.wait() 25 26 27 threading.Thread(target=run1).start() 28 threading.Thread(target=run2).start()
愿你多向优秀的人学习;
愿你不怕麻烦,勤做总结;
愿你每天都有意义;
愿你不负年华;
愿你……
愿你……
愿你……
愿你一生都快乐!