多任务的概念
操作系统可以同时运行多个任务。打个比方,你一边在用浏览器上网,一边在听MP3,一边在用Word赶作业,这就是多任务,至少同时有3个任务正在运行。还有很多任务悄咪咪地在后台同时运行着,只是桌面上没有显示而已。
并行:在计算机系统中能同时执行两个或多个处理的一种计算方法。[通俗说:一台四核计算机,每个单核cpu执行一个程序,并同时进行。简称:真的多任务]
并发:在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是同一个处理机上运行,但任一时刻点上只有一个程序在处理机上运行。[简称:假的多任务]
线程
python的thread模块是比较底层的模块,python的threading模块是对thread做了一些包装,可以更加方便的使用
1.使用threading模块
单线程执行
1 import time
2 import threading
3
4 def sing():
5 """唱歌 5秒钟"""
6 for i in range(5):
7 print("正在唱:庸人自扰")
8 time.sleep(1)
9 def main():
10 t1 = threading.Thread(target=sing)
11 t1.start()
12 if __name__ == "__main__":
13 main()
多线程执行
1 import time
2 import threading
3
4 def sing():
5 """唱歌 5秒钟"""
6 for i in range(5):
7 print("正在唱:庸人自扰")
8 time.sleep(1)
9
10 def dance():
11 """跳舞 5秒钟"""
12 for i in range(5):
13 print("----正在跳舞----")
14 time.sleep(1)
15
16 def main():
17 t1 = threading.Thread(target=sing)
18 t2 = threading.Thread(target=dance)
19 t1.start()
20 t2.start()
21 while True:
22 length = len(threading.enumerate())
23 print("当前运行的线程数为:%d" % length)
24 if length <= 1:
25 break
26
27 time.sleep(0.5)
28
29 if __name__ == "__main__":
30 main()
线程-注意
1.线程执行代码的封装
通过使用threading模块能完成多任务的程序开发,为了让每个线程的封装性更完美,所以使用threading模块时,往往会定义一个新的子类class,只要继承 threading.Thread 就可以了,然后重写run方法
示例如下:
import threading
import time
class MyThread(threading.Thread):
def ran(self):
for i in range(3):
time.sleep(1)
msg = "I'm"+self.name+"@"+str(i)
print(msg)
if __name__ == "__main__":
t = MyThread()
t.start()
多线程-共享全局变量
1 import threading
2 import time
3
4
5
6 # 定义一个全局变量
7 g_num = 100
8
9
10 def test1():
11 global g_num
12 g_num += 1
13 print("-----in test1 g_num=%d-----" % g_num)
14
15 def test2():
16 print("-----in test2 g_num=%d-----" % g_num)
17
18 def main():
19 t1 = threading.Thread(target=test1)
20 t2 = threading.Thread(target=test2)
21
22 t1.start()
23 time.sleep(1)
24
25 t2.start()
26 time.sleep(1)
27
28 print("------in main Thread g_num = %d" % g_num)
29
30 if __name__ == "__main__":
31 main()
运行结果:
-----in test1 g_num=101-----
-----in test2 g_num=101-----
------in main Thread g_num = 101
多线程开发可能遇到的问题
假设两个线程t1和t2都要对全局变量g_num(默认是0)进行加1运算,t1和t2都各对g_num加10次,g_num的最终的结果应该为20。
但是由于是多线程同时操作,有可能出现下面情况:
- 在g_num=0时,t1取得g_num=0。此时系统把t1调度为"sleeping"状态,把t2转换为“running”状态,t2也获得g_num=0
- 然后t2对得到的值进行加1并赋值给g_num,使得g_num=1
- 然后系统又把t2调度为“sleeping”,把t1转为“running”.线程t1又把它之前得到的0加1后赋值给g_num。
- 这样导致虽然t1和t2都对g_num加1,但结果仍然是g_num=1
同步的概念
同步就是协同步调,按预定的先后次序进行运行。如:你说完,我再说。
“同”应理解为协同、协助、互相配合,而不是理解为一起动作。
如进程、线程同步,可理解为进程或线程A和B一块配合,A执行到一定程度时要依靠B的某个结果,于是停下来,示意B运行;B执行,再将结果给A;A再继续操作。
解决线程同时修改全局变量的方式
对于上述提出的计算错误的问题,可以通过线程同步来进行解决
思路,如下:
- 系统调用t1,然后获取到g_num的值为0,此时上一把锁,即不允许其他线程操作g_num
- t1对g_num的值进行+1
- t1解锁,此时g_num的值为1,其他线程就可以使用g_num了,而且是g_num的值不是0而是1
- 同理其他线程在对g_num进行修改时,都要先上锁,处理完后在解锁,在上锁的整个过程中不允许其他线程访问,就保证了数据的正确性
互斥锁
当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制
线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。
互斥锁为资源引入一个状态:锁定/非锁定
某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。
threading模块中定义了Lock类,可以方便的处理锁定:
# 创建锁
mutex = threading.Lock()
# 锁定
mutex.acquire()
# 释放
mutex.release()
注意:
- 如果这个锁之前是没有上锁的,那么acquire不会堵塞
- 如果再调用acquire对这个锁上锁之前,它已经被其他线程上了锁,那么此时acquire会堵塞,直到这个锁被解锁为止
死锁
在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁。
避免死锁
- 程序设计时要尽量避免(银行家算法)
- 添加超时时间
附录–银行家算法
简而言之:可以多个锁,但需明白什么时候该解开锁A,锁A会导致锁B解开,锁B导致锁C解开以此类推。
问题描述:
一个银行家拥有一定数量的资金,有若干个客户要贷款。每个客户必须在一开始就声明他所需贷款的总额。若该客户贷款总额不超过银行家的资金总数,银行家可以接受客户的要求。客户贷款是以每次一个资金单位(如1WRMB等)的方式进行的,客户在借满所需的全部单位款额之前可能会等待,但银行家须保证这种等待是有限的,可完成的。
例如:
有三个客户C1,C2,C3,向银行家借款,该银行家的资金总额为10个资金单位,其中C1客户要借9个资金单位,C2客户要借3个资金单位,C3客户要借8个资金单位,总计20个资金单位。某一时刻的状态,如图所示: