第24课 多线程开发
一、多线程开发
1、进程和线程
1、进程的概念:运行着的程序。比如,windows下的任务管理器,里面就是各种程序的进程。
2、线程的概念:
1---每个进程里面至少包含一个线程
2---线程是操作系统创建的,用来控制代码执行的数据结构
3---线程就像代码执行的许可证
4---单线程程序,主线程的入口就是代码的开头
5---主线程顺序往下执行,直到所有的代码都执行完
2、概念对应:通过银行办理业务来解释程序运行的相关概念
1、一个服务窗口 ==CPU的一个核
2、客户 == 进程(运行着的程序)
3、调度员 == 操作系统
4、服务号 == 线程
5、调度员分配服务号给客户 == OS分配线程给进程代码
6、服务窗口给客户办理业务 == CPU核心执行线程代码
3、线程库:
1、代码通过系统调用,请求OS分配一个新的线程
2、python里面,thread和threading都可以用来创建和管理线程
3、thread比较底层---thread只有在python2中使用,python3已停用
4、threading是thread模块的扩展,提供了很多线程同步功能,使用起来更加方便强大
4、我们为什么需要多线程 :
1、多线程给一个程序并并行执行代码的能力----同时处理多个任务
$convert
>>convert 1.avi
>>convert 2.avi
2、常见的
1、UI线程
2、任务线程 task exeute
5、多线程使用共享数据
1、共享对象的概念,比如常见的自动取款机
1---某个时刻只能一个人使用
2---进入后往往会锁上门(表示已经被使用)
3---看到里面有人,外面的人排队等待
4---用完后开锁(表示已经取钱结束)
5---后一个排队的人进去取钱(重复这个过程)
2、共享数据使用场景实例
1---YY用户支付宝账号的余额为2000元
2---他乘坐滴滴打车要扣钱
3---他的余额宝会给他挣钱
4---处理滴滴打车的逻辑在线程#1里执行
5---处理余额宝挣钱的逻辑在线程#2里执行
6---今天,他坐滴滴打车扣了10块钱,假设余额宝挣的钱也是10块
3、共享数据需要用线程锁,否则可能导致数据出错。
6、条件变量-----比较难懂,工作中遇到再研究吧
二、知识点补充
1、if __name__ == __'main__' 的使用
1、有时候我们导入一个模块后,只是想使用这个模块的特定函数功能,并不想执行这个模块里面的测试代码,这时候可以使用if __name__ == "__main__"方法
实例 :m1里面有测试代码,m2调用m1模块时,并不想执行m1里面的测试代码,这时候该怎么办?
# m1模块 def sayHello(): print('hello') def sayGoodbye(): print('goodbye') # 测试代码 testing code print('exec testing') sayHello() sayGoodbye()
m2调用m1模块,m1模块内的测试代码也被执行
# m2调用m1模块 import m1 m1.sayHello() # 执行结果: D:\Python\python.exe "D:/Programs/HelloWorld2/songqin/python/lesson57 多线程/m2.py" exec testing hello goodbye hello Process finished with exit code 0
m3中,入口模块是__main__,则执行m3中的测试代码。
def sayHello(): print('hello') def sayGoodbye(): print('goodbye') print(f'm3 name is ' + __name__) # 打印m3的入口代码 if __name__ == '__main__': # 如果入口代码是__main__,则执行测试代码 print('exec testing') sayHello() sayGoodbye() # 执行结果 D:\Python\python.exe "D:/Programs/HelloWorld2/songqin/python/lesson57 多线程/m3.py" m3 name is __main__ exec testing hello goodbye Process finished with exit code 0
m4导入m3模块,这时候执行m4时,先执行m3里面的打印语句:print(f 'm3 name is' + __name__),因为m3是被引用的,这时候它的入口函数在m4里面已经变成了m3,
所以,m3里面的测试代码就不会被执行。然后再执行print(__name__),最后执行m3.sayHello()。
# m4 import m3 # 导入m3模块,这时候执行m3里面的print(f'm3 name is ' + __name__)语句,其它函数不调用不执行 print(__name__) #打印m4的入口函数名称 m3.sayHello() # 调用m3的sayHello函数 # 执行结果 D:\Python\python.exe "D:/Programs/HelloWorld2/songqin/python/lesson57 多线程/m4.py" m3 name is m3 __main__ hello Process finished with exit code 0
实例
3-1 t1 = threading.Thread(target = thread1_entry) ,threading是python的一个库,Thread是threading里面的类。
target 后面跟的是函数对象,不是函数里面的返回值,所以不能写成target = thread1_entry(), thread1_entry称为入口函数
t1只是创建了一个线程,但是并未开始执行,需要执行t1.start(),才会执行新的线程。就是执行入口函数thread1_entry里面的内容
1 print('main thread start 1!') 2 3 import threading 4 from time import sleep 5 6 def threda1_entry(): 7 print('child thread 1, start') 8 sleep(15) 9 print('child thread 1, end') 10 11 t1 = threading.Thread(target = thread1_entry) # target:对象的意思,后面跟的是函数对象,不是对象的返回值 # Thread是threading模块的类 12 t1.start() 13 t1.sleep(10) 14 print(main thread end) 15 16 17 执行结果: 18 D:\Python\python.exe "D:/Programs/HelloWorld2/songqin/python/lesson57 多线程/lesson57 知识点.py" 19 main thread start! 20 child thread 1, start 21 child thread 1, end 22 main thread end 23 24 Process finished with exit code 0
4-1
1 from time import sleep, ctime 2 3 def thread1_entry(nsec): 4 print('child thread 1, start at:', ctime()) 5 sleep(nsec) 6 print('child thread 2, start at:', ctime()) 7 8 print('main thread start.') 9 10 def thread2_entry(nsec): 11 print('child thread 1, end.', ctime()) 12 sleep(esec) 13 print('child ehread 2, end.', ctime()) 14 15 # 创建线程对象,指定了新的入口函数 16 t1 = threading.Thread(target = thread1_entry, args = (1,)) 17 t2 = threading.Thread(target = thread2_entry, args = (2,)) 18 19 # 启动新线程 20 t1.start() 21 t2.start() 22 23 # t1线程结束 24 t1.join() 25 26 # t2线程结束 27 t2.join() 28 print('main thread end.') 29 30 31 32 # 执行结果 33 34 D:\Python\python.exe "D:/Programs/HelloWorld2/songqin/python/lesson57 多线程/lesson57 知识点.py" 35 main thread start. 36 child thread 1, start at: Fri Jul 10 15:30:22 2020 37 child thread 2, start at: Fri Jul 10 15:30:22 2020 38 child thread 1, end at: Fri Jul 10 15:30:23 2020 39 child thread 2, end at: Fri Jul 10 15:30:24 2020 40 main thread end. 41 42 Process finished with exit code 0
5-1 多线程使用共享数据
1 import threading 2 from time import sleep 3 4 def thread_entry(): 5 6 # 注意,局部变量var的值,同时被两个线程调用,会搞混乱吗? 7 var = 1 8 for i in range(10): 9 # threading.currentThread().ident 获取当前线程的ID号 10 print('th #{}:{}'.format(threading.currentThread().ident, var)) 11 sleep(1) 12 var += 1 13 14 15 print('main thread start.') 16 17 t1 = threading.Thread(target=thread_entry) 18 t2 = threading.Thread(target=thread_entry) 19 20 21 t1.start() 22 t2.start() 23 24 t1.join() 25 t2.join() 26 27 print('main thread end.') 28 29 30 # 执行结果: 31 D:\Python\python.exe "D:/Programs/HelloWorld2/songqin/python/lesson57 多线程/lesson57 知识点.py" 32 main thread start. 33 th #8788:1 34 th #7524:1 35 th #8788:2th #7524:2 36 37 th #7524:3th #8788:3 38 39 th #8788:4th #7524:4 40 41 th #7524:5 42 th #8788:5 43 th #8788:6th #7524:6 44 45 th #8788:7 46 th #7524:7 47 th #7524:8 48 th #8788:8 49 th #8788:9 50 th #7524:9 51 th #8788:10th #7524:10 52 53 main thread end. 54 55 Process finished with exit code 0
5-2-2 共享数据出现的问题,结果跟预期不符
import threading from time import sleep zhifubao = { 'YY' : 2000, 'dabao' : 50000, 'liming' : 6005000 } # 线程1 :滴滴打车处理,参数是用户账户扣款金额 def thread1_didi_pay(account, amount): print('t1: get balance from bank') balance = zhifubao[account] # 下面的sleep(2)表示一些处理过程需要花上2秒钟 print('* t1: do something(like discount lookup) for 2 second') sleep(2) print('* t1: deduct') zhifubao[account] = balance - amount # 线程2:余额宝处理,参数是用户账户和当前利息 def thread2_yuebao_interest(account, amount): print('$ t2: get balance from bank') balance = zhifubao[account] # 下面的sleep(1)表示一些处理过程需要花上1秒钟 print('$ t2: do something2 ...for 1 seconds') sleep(1) print('$ t2: add') zhifubao[account] = balance + amount t1 = threading.Thread(target= thread1_didi_pay, args=('YY', 10)) t2 = threading.Thread(target= thread2_yuebao_interest, args=('YY', 10)) t1.start() t2.start() t1.join() t2.join() print('finally, YY balance is %s' % zhifubao['YY']) 执行结果: D:\Python\python.exe "D:/Programs/HelloWorld2/songqin/python/lesson57 多线程/lesson57 知识点.py" t1: get balance from bank * t1: do something(like discount lookup) for 2 second $ t2: get balance from bank $ t2: do something2 ...for 1 seconds $ t2: add * t1: deduct finally, YY balance is 1990 Process finished with exit code 0
5-3-1 使用线程锁,避免共享数据出错
1 import threading 2 from time import sleep 3 4 5 zhifubao = { 6 'YY' : 2000, 7 'lming' : 365000, 8 'zhaolei' : 6005000 9 } 10 11 12 13 # 调用一个lock函数,返回一个锁对象 14 zhifubao_lock = threading.Lock() 15 16 17 def thread1_didi_pay(account, amount): 18 # 在代码访问共享对象之前,加锁 19 # 当多个线程同时执行lock.acquire时,只有一个线程能成功获取锁,然后继续执行代码 20 # 其他线程继续等待,直到获得锁为止 21 zhifubao_lock.acquire() 22 print('* t1: get balance from bank') 23 balance = zhifubao[account] 24 25 print('* t1: do something(like discount lookup) for 2 seconds') 26 sleep(2) 27 28 29 print('*t1: deduct') 30 zhifubao[account] = balance - amount 31 32 33 # 访问完共享对象后,释放锁 34 # 访问结束后,一定要调用lock对象的acquire方法,进行解锁操作 35 # 否则其它等待锁的线程将永远等待下去,成为死线程 36 zhifubao_lock.release() 37 38 39 def thread2_yuebao_interest(account, amount): 40 # 在代码访问共享对象之前,加锁 41 zhifubao_lock.acquire() 42 print('$ t2: get balance from bank') 43 balance = zhifubao[account] 44 45 print('$ t2: do something2...for 1 seconds') 46 sleep(1) 47 48 print('$ t2: add') 49 zhifubao[account] = balance + amount 50 zhifubao_lock.release() 51 52 t1 = threading.Thread(target=thread1_didi_pay, args=('YY', 2000)) 53 t2 = threading.Thread(target=thread2_yuebao_interest, args=('YY', 2000)) 54 55 t1.start() 56 t2.start() 57 t1.join() 58 t2.join() 59 60 print('finally, YY balance is %s:' % zhifubao['YY']) 61 62 63 64 # 执行结果 65 66 D:\Python\python.exe "D:/Programs/HelloWorld2/songqin/python/lesson57 多线程/lesson57 知识点.py" 67 * t1: get balance from bank 68 * t1: do something(like discount lookup) for 2 seconds 69 *t1: deduct 70 $ t2: get balance from bank 71 $ t2: do something2...for 1 seconds 72 $ t2: add 73 finally, YY balance is 2000: 74 75 Process finished with exit code 0