linux : 多对象 多任务
import time
def sing():
'''唱歌 5秒'''
for i in range(5):
print('---正在唱歌----')
time.sleep(1) # 延时一秒
def dance():
'''跳舞 5秒'''
for i in range(5):
print('---正在跳舞---')
time.sleep(1) # 延时一秒
def main():
sing()
dance()
if __name__ == '__main__':
main()
结果:
D:\Users\sunny\Anaconda3\python.exe F:/python学习/01-python基础/hm_01_hello.py
---正在唱歌----
---正在唱歌----
---正在唱歌----
---正在唱歌----
---正在唱歌----
---正在跳舞---
---正在跳舞---
---正在跳舞---
---正在跳舞---
---正在跳舞---
Process finished with exit code 0
这个代码总共执行了10秒,且唱歌和跳舞不能同时进行。
改进一下,让唱歌和跳舞同时进行
import time
import threading
def sing():
'''唱歌 5秒'''
for i in range(5):
print('---正在唱歌----')
time.sleep(1) # 延时一秒
def dance():
'''跳舞 5秒'''
for i in range(5):
print('---正在跳舞---')
time.sleep(1) # 延时一秒
def main():
t1 = threading.Thread(target=sing)
t2 = threading.Thread(target=dance)
t1.start()
t2.start()
if __name__ == '__main__':
main()
运行结果:
D:\Users\sunny\Anaconda3\python.exe F:/python学习/01-python基础/hm_01_hello.py
---正在唱歌----
---正在跳舞---
---正在跳舞---
---正在唱歌----
---正在唱歌----
---正在跳舞---
---正在跳舞---
---正在唱歌----
---正在唱歌----
---正在跳舞---
Process finished with exit code 0
总共运行了5秒。这就涉及了 多任务。
并行:真的多任务
并发:假的多任务。任务数大于cpu 核数。
大部分都是并发。因为电脑上同时运行的任务数一般都是大于你的电脑核数的(现在基本是4核)
一个程序运行起来之后,一定有一个执行代码的东西,这个东西就被称为 线程
查看线程数量threading.enumerate()
程序如下所示:
import time
import threading
def sing():
'''唱歌 5秒'''
for i in range(5):
print('---正在唱歌----')
def dance():
'''跳舞 5秒'''
for i in range(5):
print('---正在跳舞---')
def main():
t1 = threading.Thread(target=sing)
t2 = threading.Thread(target=dance)
t1.start()
time.sleep(1)
print('----1-----')
t2.start()
time.sleep(1)
print ('-----2----')
print (threading.enumerate())
if __name__ == '__main__':
main()
运行结果:
D:\Users\sunny\Anaconda3\python.exe F:/python学习/01-python基础/hm_01_hello.py
---正在唱歌----
---正在唱歌----
---正在唱歌----
---正在唱歌----
---正在唱歌----
----1-----
---正在跳舞---
---正在跳舞---
---正在跳舞---
---正在跳舞---
---正在跳舞---
-----2----
[<_MainThread(MainThread, started 15664)>]
Process finished with exit code 0
线程的运行是没有先后顺序的。所以必须加延时。
这里有三个线程,一个主线程,查看线程数量threading.enumerate()
,两个子线程:唱歌 和 跳舞 。
import time
import threading
def sing():
'''唱歌 2秒'''
for i in range(2):
print('---正在唱歌----')
time.sleep(1)
def dance():
'''跳舞 5秒'''
for i in range(5):
print('---正在跳舞---')
time.sleep(1)
def main():
t1 = threading.Thread(target=sing)
t2 = threading.Thread(target=dance)
t1.start()
t2.start()
while True:
print (threading.enumerate())
if len(threading.enumerate()) <= 1:
break
time.sleep(1)
if __name__ == '__main__':
main()
运行结果:
D:\Users\sunny\Anaconda3\python.exe F:/python学习/01-python基础/hm_01_hello.py
---正在唱歌----
---正在跳舞---
[<Thread(Thread-2, started 20804)>, <Thread(Thread-1, started 59508)>, <_MainThread(MainThread, started 34292)>]
---正在跳舞---
---正在唱歌----
[<Thread(Thread-2, started 20804)>, <Thread(Thread-1, started 59508)>, <_MainThread(MainThread, started 34292)>]
---正在跳舞---
[<Thread(Thread-2, started 20804)>, <Thread(Thread-1, started 59508)>, <_MainThread(MainThread, started 34292)>]
[<Thread(Thread-2, started 20804)>, <_MainThread(MainThread, started 34292)>]
---正在跳舞---
---正在跳舞---
[<Thread(Thread-2, started 20804)>, <_MainThread(MainThread, started 34292)>]
[<_MainThread(MainThread, started 34292)>]
Process finished with exit code 0
在这里程序中,把 唱歌 设置为2秒,跳舞设置为 5秒。看结果 ,我们可以看到,前两秒,有三个线程。2秒到5秒,有两个线程。5秒之后 ,只有一个主线程,这时跳出循环,程序结束。
所有 ,主线程运行时间 必须大于 子线程运行时间,要不然程序会死掉。
当创建Thread的时候,不会创建 线程
当调用Thread 创建出来的实例对象的start 方法的时候,才会创建线程以及这个线程开始运行。
定义一个线程,上文中的程序用的方法都是threading.Thread(target=函数名)
其实如果这个线程比较复杂,我们还可以使用类和继承,形式如下:
调用start 方法时,run 函数里写的什么,就运行什么。
当一个线程比较复杂,且分为多个函数来做的时候,最好用类的方法。
import threading
class MyThread(threading.Thread):
def run(self):
pass
def login(self):
pass
def register(self):
pass
if __name__ == '__main__':
t = MyThread()
t.start()
这个程序,当 start时,运行的是子线程中的 run 函数中的内容,那么,其他两个函数怎么调用呢?在主程序中吗?肯定不是。
应该在类中,自己调用。已达到使用 start 方法能够调用run 函数和其他两个函数。
在类中 这样定义;
self.login()
self.register()
主线程会等待所有线程结束之后再结束。
线程共享全局变量
在一个函数中,对全局变量进行修改的时候,是否需要使用 global 进行说明,要看是否对全局变量的执行指向进行了修改,如果修改了执行,即让全局变量指向了一个新的地方,那么必须使用 global 。如果仅仅是修改了指向的空间中的数据,那么不必使用 global 。下面用程序验证一下。
a = 1
b = [1,2]
def test():
global a
a += 1
def test2():
b.append(3)
print('a: %d' % a )
print( b )
test()
test2()
print('a: %d' % a )
print( b )
现在看一下,线程之间的全局变量是不是共享的:
import threading
import time
a = 1
def test1():
global a
a += 1
print('---in test1 a=%d---' % a)
def test2():
print('---in test2 a=%d---' % a)
def main():
t1 = threading.Thread(target=test1)
t2 = threading.Thread(target=test2)
t1.start()
time.sleep(1)
t2.start()
time.sleep(1)
print('---in main thread a=%d' % a)
if __name__ == '__main__':
main()
可以看出,线程之间的全局变量 是 共享的
现在我们用元组来进行测试,这里再补充一个知识点:线程创建实例时,不仅可以指定函数名 target=***, 还可以指定传递的参数 args=*** 。但是传递的必须是一个元组。
import threading
import time
def test1(a):
a.append(3)
print('---in test1 a=%s---' % str(a))
def test2(a):
print('---in test2 a=%s---' % str(a))
a = [1,2]
def main():
t1 = threading.Thread(target=test1, args=(a,)) # 必须传递一个元组
t2 = threading.Thread(target=test2, args=(a,))
t1.start()
time.sleep(1)
t2.start()
time.sleep(1)
print('---in main thread a=%s' % str(a))
if __name__ == '__main__':
main()
在抓取数据的过程中,肯定是多线程,一个线程负责抓取数据,一个线程负责处理数据,一个线程负责把数据传递给客户。这三个线程之间的数据必须是共享全局变量的。
但是全局变量共享是不是有什么危害呢? 会造成资源竞争。
import threading
import time
a = 0
def test1(num):
global a
for i in range(num):
a += 1
print('---in test1 a=%d---' % a)
def test2(num):
global a
for i in range(num):
a += 1
print('---in test2 a=%d---' % a)
def main():
t1 = threading.Thread(target=test1, args=(100,)) # 必须传递一个元组
t2 = threading.Thread(target=test2, args=(100,))
t1.start()
t2.start()
time.sleep(3)
print('---in main thread a=%d' % a)
if __name__ == '__main__':
main()
test1 将 a+ 1 执行 100 次,
test2 将 a + 1 执行 100 次,
由于线程之间没有先后顺序,但是结果肯定是加了200.所以上面的结果是对的。下面我们将循环次数加大:
t1 = threading.Thread(target=test1, args=(1000000,)) # 必须传递一个元组
t2 = threading.Thread(target=test2, args=(1000000,))
结果为:
按理说结果应该为:2000000 。这就是因为资源竞争而导致的问题。
那么怎么解决:使用同步或者异步方法。
下面使用互斥锁解决资源竞争的问题:
import threading
import time
a = 0
def test1(num):
global a
# 上锁,如果之前没有上锁,那么此时直接上锁
#如果此时已经上锁了,那么就会被堵塞在这里,直到这个锁被释放。
mutex.acquire()
for i in range(num):
a += 1
# 解锁
mutex.release()
print('---in test1 a=%d---' % a)
def test2(num):
global a
mutex.acquire()
for i in range(num):
a += 1
mutex.release()
print('---in test2 a=%d---' % a)
# 创建一个互斥锁
mutex = threading.Lock()
def main():
t1 = threading.Thread(target=test1, args=(1000000,)) # 必须传递一个元组
t2 = threading.Thread(target=test2, args=(1000000,))
t1.start()
t2.start()
time.sleep(3)
print('---in main thread a=%d' % a)
if __name__ == '__main__':
main()
结果如下:
这是一种解决方案,先让test1 执行完,再去执行test2 ,这样就会导致,如果线程1运行时间是100秒,就会等待上锁100秒。直到执行完解锁,线程2才会被执行。所以,我们上锁的程序应该是越少越好。再看另一种解决方案,
import threading
import time
a = 0
def test1(num):
global a
for i in range(num):
mutex.acquire()
a += 1
mutex.release()
print('---in test1 a=%d---' % a)
def test2(num):
global a
for i in range(num):
mutex.acquire()
a += 1
mutex.release()
print('---in test2 a=%d---' % a)
# 创建一个互斥锁
mutex = threading.Lock()
def main():
t1 = threading.Thread(target=test1, args=(1000000,)) # 必须传递一个元组
t2 = threading.Thread(target=test2, args=(1000000,))
t1.start()
t2.start()
time.sleep(3)
print('---in main thread a=%d' % a)
if __name__ == '__main__':
main()
运行结果:
两次运行结果对比可以看出:最终都是加了2000000,但是两个子线程的结果却不一样。是因为,第一种上锁是加完1000000,再加1000000。所以结果固定。第二种上锁,可能是线程1先上锁,可能执行一次以后解锁,线程2 抢到锁,执行2次解锁,然后线程1再上锁… 因为是随机的,所以结果不固定,但是总次数2000000是固定的。
一个程序里可以有多个线程,一个线程里可以有多个函数,那么一个线程里可以有多个锁吗?可以!但是多锁容易出现问题。
创建了两个类,其中各有一个锁,A B 。A B 都可以上锁,但是A B 都在等对方解锁,就会卡在这里。出现死锁。
银行家算法:
import socket
import threading
def send_masg(udp_socket, dest_ip, dest_port):
'''发送数据'''
while True:
recv_data = input('请输入要发送的信息:')
udp_socket.sendto(recv_data.encode('utf-8'), (dest_ip, dest_port))
def recv_masg(udp_socket):
'''接收数据并显示'''
while True:
recv_data = udp_socket.recvfrom(1024)
print(recv_data)
def main():
''' 完成 udp 聊天器的整体控制'''
# 1. 创建套接字
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 2. 绑定本地信息
udp_socket.bind(('',7890))
# 3. 获取对方信息
dest_ip = input('请输入对方的ip:')
dest_port = int(input('请输入对方的port:'))
# 创建两个线程,接受信息。
t1 = threading.Thread(target=send_masg, args=(udp_socket,dest_ip, dest_port))
t2 = threading.Thread(target=recv_masg, args=(udp_socket, ))
t1.start()
t2.start()
if __name__ == '__main__':
main()