简单的多任务demo:
1 import time 2 import threading 3 4 def sing(): 5 for i in range(5): 6 print("------正在唱歌-------") 7 time.sleep(1) 8 9 10 def dance(): 11 for i in range(5): 12 print("------正在跳舞-------") 13 time.sleep(1) 14 15 16 def main(): 17 target1 = threading.Thread(target=sing) 18 target2 = threading.Thread(target=dance) 19 target1.start() 20 target2.start() 21 22 23 if __name__ == '__main__': 24 main() 25 ''' 26 ------正在唱歌------- 27 ------正在跳舞------- 28 ------正在唱歌------- 29 ------正在跳舞------- 30 ------正在跳舞------- 31 ------正在唱歌------- 32 ------正在唱歌------- 33 ------正在跳舞------- 34 ------正在唱歌------- 35 ------正在跳舞------- 36 '''
任务数大于cpu核数是并发!
任务数小于cpu核数是并行!
我们的电脑上基本上属于并发的!
现在所有的多任务基本上都是假的多任务!基本都是并发。而不是并行。
上面的多任务的实现是用threading 模块中的Thread() 类实现的!
查看正在运行的线程数量:
threading 模块中的enumerate()可以返回所有的线程,是个列表。
线程的执行是没有顺序的。
1 import time 2 import threading 3 4 def sing(): 5 for i in range(5): 6 print("------正在唱歌-------") 7 time.sleep(1) 8 9 10 def dance(): 11 for i in range(5): 12 print("------正在跳舞-------") 13 time.sleep(1) 14 15 16 def main(): 17 target1 = threading.Thread(target=sing) 18 target2 = threading.Thread(target=dance) 19 target1.start() 20 target2.start() 21 22 print(threading.enumerate()) # 它是个列表,里面放的是线程对象 23 24 25 26 27 if __name__ == '__main__': 28 main() 29 ''' 30 ------正在唱歌------- 31 ------正在跳舞------- 32 [<_MainThread(MainThread, started 9608)>, <Thread(Thread-2, started 14892)>, <Thread(Thread-1, started 13340)>] 33 ------正在唱歌------- 34 ------正在跳舞------- 35 ------正在唱歌------- 36 ------正在跳舞------- 37 ------正在跳舞------- 38 ------正在唱歌------- 39 ------正在跳舞------- 40 ------正在唱歌------- 41 '''
如果创建Thread的时候执行的函数结束,就是这个线程结束了!
如下来验证上述观点:
1 import time 2 import threading 3 4 def sing(): 5 for i in range(5): 6 print("------正在唱歌-------") 7 time.sleep(1) 8 9 10 def dance(): 11 for i in range(10): 12 print("------正在跳舞-------") 13 time.sleep(1) 14 15 16 def main(): 17 target1 = threading.Thread(target=sing) 18 target2 = threading.Thread(target=dance) 19 target1.start() 20 target2.start() 21 22 while True: 23 time.sleep(1) 24 print(threading.enumerate()) # 它是个列表,里面放的是线程对象 25 26 27 if __name__ == '__main__': 28 main() 29 ''' 30 ------正在唱歌------- 31 ------正在跳舞------- 32 ------正在跳舞------- 33 [<Thread(Thread-1, started 1092)>, <_MainThread(MainThread, started 2780)>, <Thread(Thread-2, started 14820)>] 34 ------正在唱歌------- 35 ------正在唱歌------- 36 ------正在跳舞------- 37 [<Thread(Thread-1, started 1092)>, <_MainThread(MainThread, started 2780)>, <Thread(Thread-2, started 14820)>] 38 ------正在跳舞------- 39 ------正在唱歌------- 40 [<Thread(Thread-1, started 1092)>, <_MainThread(MainThread, started 2780)>, <Thread(Thread-2, started 14820)>] 41 ------正在唱歌------- 42 [<Thread(Thread-1, started 1092)>, <_MainThread(MainThread, started 2780)>, <Thread(Thread-2, started 14820)>] 43 ------正在跳舞------- 44 [<Thread(Thread-1, started 1092)>, <_MainThread(MainThread, started 2780)>, <Thread(Thread-2, started 14820)>] 45 ------正在跳舞------- 46 [<_MainThread(MainThread, started 2780)>, <Thread(Thread-2, started 14820)>] 47 ------正在跳舞------- 48 [<_MainThread(MainThread, started 2780)>, <Thread(Thread-2, started 14820)>] 49 ------正在跳舞------- 50 [<_MainThread(MainThread, started 2780)>, <Thread(Thread-2, started 14820)>] 51 ------正在跳舞------- 52 [<_MainThread(MainThread, started 2780)>, <Thread(Thread-2, started 14820)>] 53 ------正在跳舞------- 54 [<_MainThread(MainThread, started 2780)>, <Thread(Thread-2, started 14820)>] 55 [<_MainThread(MainThread, started 2780)>] 56 [<_MainThread(MainThread, started 2780)>] 57 [<_MainThread(MainThread, started 2780)>] 58 [<_MainThread(MainThread, started 2780)>] 59 '''
我们可以通过threading 中的enumrate() 得到的列表的个数来判断子线程是否执行完毕!
什么时候创建子线程,什么时候运行子线程?
它们都是从start() 开始的,执行到start 的时候创建子线程!并且开始执行子线程。
证明如下:
1 import time 2 import threading 3 4 def sing(): 5 for i in range(5): 6 print("------正在唱歌-------") 7 time.sleep(1) 8 9 def main(): 10 print(threading.enumerate()) 11 target1 = threading.Thread(target=sing) 12 print(threading.enumerate()) 13 target1.start() 14 print(threading.enumerate()) 15 16 if __name__ == '__main__': 17 main() 18 ''' 19 [<_MainThread(MainThread, started 12700)>] 20 [<_MainThread(MainThread, started 12700)>] 21 ------正在唱歌------- 22 [<Thread(Thread-1, started 14500)>, <_MainThread(MainThread, started 12700)>] 23 ------正在唱歌------- 24 ------正在唱歌------- 25 ------正在唱歌------- 26 ------正在唱歌------- 27 '''
调用threading.Thread() 类的时候,只是创建了一个普通的对象。
而thread.start() 是真实创建线程并开始执行。
到此,我们知道了一下几点:
1,线程的执行没有顺序,(可通过sleep 达到我们所期望的要求)
2,线程的创建和执行都是从start() 之后开始的,线程的结束是对应的函数执行完毕,结束子线程!
3,默认主线程会等子线程先执行完毕。
通过继承Thread类完成创建线程:
从上面我们知道,我们可以通过threading.Thread()中的target 可以指定一个函数名,从而实现一个线程的创建(真实创建和执行是start)。
但是,我们现在要创建的线程比较的复杂,不是简单的函数就能完成的,这时我们要通过继承threading.Thread() 这个类,然后我们必须要重写run() 方法
Thread() 类的两种使用方式 :
继承 Thread的使用方式:
1 # -*- coding:utf8 -*- 2 #Author: ZCB 3 4 import threading 5 import time 6 7 class MyThread(threading.Thread): 8 def run(self): 9 for i in range(3): 10 time.sleep(1) 11 print("I am "+self.name) 12 13 if __name__ == '__main__': 14 thread1 = MyThread() 15 thread1.start() 16 17 ''' 18 输出: 19 I am Thread-1 20 I am Thread-1 21 I am Thread-1 22 '''
其中的self.name 是线程的名字。
这种适合的是一个线程里面做的事情比较复杂,可以多个函数来做事情。
也许你可能会觉得调用的时候应该用thread1.run() 。
这里我们看官方文档中的内容:
它主要介绍了start() 的用法,它说的是对于每一个线程对象,必须用start() 调用,
start()复杂的调用run() 方法。
而且,如果一个线程对象调用多次start() 会抛出 RuntimeError 异常。
如果用继承方式使用多线程的话,此时的传参要注意:
1 # -*- coding:utf8 -*- 2 #Author: ZCB 3 4 import threading 5 import time 6 7 class MyThread(threading.Thread): 8 def __init__(self,args =None,kwargs=None): 9 super().__init__(args = None,kwargs=None) 10 self.name = kwargs["name"] 11 self.age = kwargs["age"] 12 self.list = args[0] 13 14 def run(self): 15 self.test() 16 17 def test(self): 18 print("Name :{},Age:{}".format(self.name,self.age)) 19 for i in self.list: 20 print(i,end=" ") 21 22 23 if __name__ == '__main__': 24 thread1 = MyThread(args=([1,2,3,4,5],) ,kwargs={"name":"tom","age":18}) 25 thread1.start() 26 for i in range(5,11): 27 print(i ,end=" ") 28 29 ''' 30 输出: 31 Name :tom,Age:18 32 5 6 7 8 9 10 1 2 3 4 5 33 '''
前面第一种多线程的使用中,如果需要传递参数:
1 # -*- coding:utf8 -*- 2 #Author: ZCB 3 4 import threading 5 import time 6 7 def test(ls,dt): 8 for i in ls: 9 print(i,end=" ") 10 print("Name: {} Age: {}".format(dt["name"],dt["age"])) 11 12 def main(): 13 ls = [1,2,3,4,5] 14 dt = {"name":"tom","age":18} 15 a = [1,2,3,4,5] 16 thread1 = threading.Thread(target=test,args=(ls,dt)) 17 thread1.start() 18 19 if __name__ == '__main__': 20 main() 21 print("I am Main_Thread!") 22 for i in range(6,11): 23 print(i)
1 # -*- coding:utf8 -*- 2 #Author: ZCB 3 4 import threading 5 import time 6 7 def test(ls,name,age): 8 for i in ls: 9 print(i,end=" ") 10 print("Name: {} Age: {}".format(name,age)) # 注意,如果是用kwargs 传递字典的话,要用字典的key 做形参 11 12 def main(): 13 ls = [1,2,3,4,5] 14 dt = {"name":"tom","age":18} 15 a = [1,2,3,4,5] 16 thread1 = threading.Thread(target=test,args=(ls,),kwargs=dt) 17 thread1.start() 18 19 if __name__ == '__main__': 20 main() 21 print("I am Main_Thread!") 22 for i in range(6,11): 23 print(i)
总结本质上都是通过args 和kwargs 来传参的。
多线程共享全局变量:
首先,说下如果想要操作全局变量,到底什么时候加global ,什么时候不用加:
1 # -*- coding:utf8 -*- 2 #Author: ZCB 3 4 ############################全局变量相关############################### 5 6 # num = 15 7 # def test(): 8 # global num #此时操作的num 为全局变量 9 # num +=1 10 # test() 11 # print(num) # 此时为16 12 13 14 # num = [1,2,3] 15 # def test(): 16 # num.append(4) #此时操作的num 是全局变量 17 # test() 18 # print(num) #此时为 【1 2 3 4 】 19 20 # num = [1,2,3] 21 # def test(): 22 # num +=[4,5] #此时操作的num 就不是全局变量了 ,不能这样写,会报错 23 # test() 24 # print(num) 25 26 num = [1,2,3] 27 def test(): 28 global num 29 num +=[4,5] #此时操作的num 就不是全局变量了 ,不能这样写,会报错 30 test() 31 print(num) 32 33 #那么问题来了,操作全局变量的时候,到底什么时候加global , 34 #记住,当指针没发生变化的时候,不用加,发生变化必须加 35 36 ############################全局变量相关###############################
下面用程序来验证,多线程是共享全局变量的:
1 # -*- coding:utf8 -*- 2 #Author: ZCB 3 4 import threading 5 import time 6 7 num = 100 8 9 10 def test(): 11 global num 12 num +=1 13 14 def main(): 15 thread1 = threading.Thread(target=test) 16 thread1.start() 17 time.sleep(1) 18 print(num) 19 20 if __name__ == '__main__': 21 main()
输出是 101 ,这说明主线程和创建的子线程是共享全局变量的。
多线程共享全局变量的问题:
资源竞争。
当循环次数为100的时候:
1 # -*- coding:utf8 -*- 2 #Author: ZCB 3 4 import threading 5 import time 6 7 num = 0 8 9 def test01(): 10 global num 11 for i in range(100): 12 num = num +1 13 print("in test01 {}".format(num)) 14 15 def test02(): 16 global num 17 for i in range(100): 18 num = num +1 19 print("in test02 {}".format(num)) 20 21 def main(): 22 thread1 = threading.Thread(target=test01) 23 thread2 = threading.Thread(target=test02) 24 25 thread1.start() 26 thread2.start() 27 28 time.sleep(2) 29 30 print("in main {}".format(num)) 31 32 if __name__ == '__main__': 33 main() 34 ''' 35 输出: 36 in test01 100 37 in test02 200 38 in main 200 39
结果是正常的
次数为一千,一万,十万都是正常的,
但是如果循环次数为一百万的话,此时就要出错了。
1 # -*- coding:utf8 -*- 2 #Author: ZCB 3 4 import threading 5 import time 6 7 num = 0 8 9 def test01(): 10 global num 11 for i in range(1000000): 12 num = num +1 13 print("in test01 {}".format(num)) 14 15 def test02(): 16 global num 17 for i in range(1000000): 18 num = num +1 19 print("in test02 {}".format(num)) 20 21 def main(): 22 thread1 = threading.Thread(target=test01) 23 thread2 = threading.Thread(target=test02) 24 25 thread1.start() 26 thread2.start() 27 28 time.sleep(2) 29 30 print("in main {}".format(num)) 31 32 if __name__ == '__main__': 33 main() 34 ''' 35 输出: 36 in test01 1206988 37 in test02 1191407 38 in main 1191407 39 40 这里的问题是: 41 在执行num =num +1 的时候,cpu 会做很多语句 42 1,获取num 值 43 2,将num 值加1 44 2,将第二步得到的值写回到num 对应的内存中。 45 46 这里可能出现如下情况: 47 假设num = 100时, 48 对于线程1,它可能执行了前两步 但是,它还没将得到的101写回到内存, 49 此时轮到线程2来执行了,它也是执行了前两步,也没把101写回到内存, 50 此时线程1再次执行,将101写回到内存, 51 然后线程2也执行,又将101写回到内存。 52 53 所以,此时两个线程都执行了一遍,但是num 的结构确实101 , 54 而且随着次数的增加,这种可能性出现的概率就越大。 55 '''
在执行num =num +1 的时候,cpu 会做很多语句
1,获取num 值
2,将num 值加1
2,将第二步得到的值写回到num 对应的内存中。
这里可能出现如下情况:
假设num = 100时,
对于线程1,它可能执行了前两步 但是,它还没将得到的101写回到内存,
此时轮到线程2来执行了,它也是执行了前两步,也没把101写回到内存,
此时线程1再次执行,将101写回到内存,
然后线程2也执行,又将101写回到内存。
所以,此时两个线程都执行了一遍,但是num 的结果确是101 ,
而且随着次数的增加,这种可能性出现的概率就越大。
同步概念:
上述出现的问题,如何解决呢?其实,这种多线程的问题在任何的编程语言中都是存在的。它是由于操作系统导致发生的这种情况。
如何解决呢?
其实,对于每个线程来说,它们不能只完成前两步,而没把最终结果写回内存,它应该是要么不做,要么就做完,这称为原子性。
银行的存取款业务肯定也得是这样。不能一个线程取钱2步,另一个也取钱2步,然后,它们两个分别写回内存。就做到取了两份钱,银行却只扣了一份。
同步就是协同步调,按预定的先后次序进行运行。如:你说完,我再说。
如何解决上述的问题呢?
用下面的东西:
互斥锁:
1 # -*- coding:utf8 -*- 2 #Author: ZCB 3 4 import threading 5 import time 6 7 num = 0 8 9 def test01(): 10 metux.acquire() 11 global num 12 for i in range(1000000): 13 num = num +1 14 print("in test01 {}".format(num)) 15 metux.release() 16 17 18 def test02(): 19 metux.acquire() 20 global num 21 for i in range(1000000): 22 num = num +1 23 print("in test02 {}".format(num)) 24 metux.release() 25 26 metux = threading.Lock() 27 28 def main(): 29 thread1 = threading.Thread(target=test01) 30 thread2 = threading.Thread(target=test02) 31 32 thread1.start() 33 thread2.start() 34 35 time.sleep(2) 36 37 print("in main {}".format(num)) 38 39 if __name__ == '__main__': 40 main() 41 ''' 42 in test01 1000000 43 in test02 2000000 44 in main 2000000 45 '''
此时,就已经解决了上述的问题了。
但是,上述的代码不好,上锁锁住的代码太多了,如果第一个锁上之后,它要执行100s ,那么第二个线程就只能等着了。
其实上锁有个原则,锁的代码越少越好。
其实,我们只需对关键的几步上锁就行了。
如下:
1 # -*- coding:utf8 -*- 2 #Author: ZCB 3 4 import threading 5 import time 6 7 num = 0 8 9 def test01(): 10 global num 11 for i in range(1000000): 12 metux.acquire() 13 num = num +1 14 metux.release() 15 16 print("in test01 {}".format(num)) 17 18 19 def test02(): 20 global num 21 for i in range(1000000): 22 metux.acquire() 23 num = num +1 24 metux.release() 25 print("in test02 {}".format(num)) 26 27 metux = threading.Lock() 28 29 def main(): 30 thread1 = threading.Thread(target=test01) 31 thread2 = threading.Thread(target=test02) 32 33 thread1.start() 34 thread2.start() 35 36 time.sleep(2) 37 38 print("in main {}".format(num)) 39 40 if __name__ == '__main__': 41 t1 = time.perf_counter() 42 main() 43 print(time.perf_counter()-t1) 44 ''' 45 in test02 1981093 46 in test01 2000000 47 in main 2000000 48 2.019051503742717 49 '''
此时,最终的结果是不变的。
上面,将锁放到 for 循环外面的用时::2.0241010994337283
上面,将锁放到 for 循环里面的用时::2.019051503742717
死锁:
程序中可能会有多个互斥锁,这时可能就出现问题了。
死锁是一种状态,就是一个线程等待对方释放锁,可是对方却也在等我释放锁。
1 # -*- coding:utf8 -*- 2 #Author: ZCB 3 4 import threading 5 import time 6 7 class MyThread1(threading.Thread): 8 def run(self): 9 # 对mutexA上锁 10 mutexA.acquire() 11 12 # mutexA上锁后,延时1秒,等待另外那个线程 把mutexB上锁 13 print(self.name+'----do1---up----') 14 time.sleep(1) 15 16 # 此时会堵塞,因为这个mutexB已经被另外的线程抢先上锁了 17 mutexB.acquire() 18 print(self.name+'----do1---down----') 19 mutexB.release() 20 21 # 对mutexA解锁 22 mutexA.release() 23 24 class MyThread2(threading.Thread): 25 def run(self): 26 # 对mutexB上锁 27 mutexB.acquire() 28 29 # mutexB上锁后,延时1秒,等待另外那个线程 把mutexA上锁 30 print(self.name+'----do2---up----') 31 time.sleep(1) 32 33 # 此时会堵塞,因为这个mutexA已经被另外的线程抢先上锁了 34 mutexA.acquire() 35 print(self.name+'----do2---down----') 36 mutexA.release() 37 38 # 对mutexB解锁 39 mutexB.release() 40 41 mutexA = threading.Lock() 42 mutexB = threading.Lock() 43 44 if __name__ == '__main__': 45 t1 = MyThread1() 46 t2 = MyThread2() 47 t1.start() 48 t2.start()
1 # -*- coding:utf8 -*- 2 #Author: ZCB 3 4 import threading 5 import time 6 7 def test1(): 8 lock1.acquire() 9 print("in test1") 10 time.sleep(1) 11 lock2.acquire 12 13 14 def test2(): 15 lock2.acquire() 16 print("in test2") 17 time.sleep(1) 18 lock1.acquire() 19 20 lock1 = threading.Lock() 21 lock2 = threading.Lock() 22 def main(): 23 thread1 = threading.Thread(target=test1) 24 thread2 = threading.Thread(target=test2) 25 thread1.start() 26 thread2.start() 27 28 if __name__ == '__main__': 29 main()
如何避免死锁:
有很多方法:
1,程序设计的时候要尽量避免(银行家算法)
2,添加超时时间
3,使用递归锁(当使用多个互斥锁的时候,用递归锁代替)
递归锁的实现的原理是:其实内部就是个计数器。只要是它大于0,其他人就不可能拿到这把锁。
1 # -*- coding:utf8 -*- 2 #Author: ZCB 3 4 import threading 5 import time 6 7 def test1(): 8 r_lock.acquire() #内部计数器为1 9 print("in test1") 10 time.sleep(1) 11 r_lock.acquire() #内部计数器为2 12 13 14 def test2(): 15 r_lock.acquire() 16 print("in test2") 17 time.sleep(1) 18 r_lock.acquire() 19 20 r_lock = threading.RLock() 21 22 def main(): 23 thread1 = threading.Thread(target=test1) 24 thread2 = threading.Thread(target=test2) 25 thread1.start() 26 thread2.start() 27 28 if __name__ == '__main__': 29 main()
所以,如果在一个线程中想使用多个锁的时候,可以尝试使用递归锁!
案例-多线程版udp聊天器:
1 from socket import * 2 import threading 3 4 def recvData(udp_socket): 5 while True: 6 recv_data= udp_socket.recvfrom(1024) 7 print(recv_data[0].decode("utf-8")) 8 9 def sendData(udp_socket,c_addr): 10 while True: 11 send_data = input("to client >>>") 12 udp_socket.sendto(send_data.encode("utf-8"),c_addr) 13 14 15 def main(): 16 udp_socket = socket(AF_INET,SOCK_DGRAM) 17 18 udp_socket.bind(("",9090)) 19 20 dest_ip = input("请输入ip ") 21 dest_port = int(input("请输入 port")) 22 23 c_addr = (dest_ip,dest_port) 24 thread1 = threading.Thread(target=recvData,args=(udp_socket,)) 25 thread2 = threading.Thread(target=sendData,args=(udp_socket,c_addr)) 26 27 thread1.start() 28 thread2.start() 29 30 if __name__ == '__main__': 31 main()
这里主要做的是用两个线程,一个专门管接收数据。一个专门管发送数据!