3.1,多任务的介绍
-
现实生活中很多事情是同时进行的,唱歌跳舞,手掌握方向盘,脚踩油门
-
多任务代码案例
import time import threading def sing(): for i in range(5): print('正在唱歌') time.sleep(1) def dance(): 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()
运行结果:
-
并发和并行
-
并发(运行的程序大于cpu核数)
:假的多任务,类似单片机的动态扫描,如果cpu只有1个核,他是这样运行的:先给第一个程序运行0.001秒,让第二个程序0.001秒,第三个0.001秒,像这样循环,达到肉眼看不到的速度,就变成了多任务 -
并行(运行的程序小于cpu核数)
:真正的多任务,一个核处理一个程序
-
3.2,线程(重点)
python的 thread 模块时比较底层的模块,python的 threading 模块时对thread做了一些包装的,可以更加方便的被使用
-
使用threading模块
单线程执行
# encoding = utf-8 import time def sayHi(): print('hello, 今天天气真好') time.sleep(1) if __name__ == '__main__': for _ in range(5): sayHi()
多线程执行
# encoding = utf-8 import time import threading def sayHi(): print('hello, 今天天气真好') time.sleep(1) if __name__ == '__main__': for _ in range(5): t1 = threading(target=sayHi) # 创建一条子线程 t1.start()
import threading import time class MyThread(threading.Thread): def run(self): for i in range(3): time.sleep(1) msg = 'I\'m ' + self.name + '@' + str(i) print(msg) def login(self): pass def register(self): pass if __name__ == '__main__': t = MyThread() t1 = MyThread() t.start() t1.start()
多线程执行流程:
主线程执行,执行到
t1.start()
的时候,开辟一条子线程,子线程去执行t1
里面的任务,主线程不管,继续执行主线程,又碰到t1.start()
,又开辟一条子线程,去执行t2
里面的任务,等到主线程执行结束,看看有没有子线程在执行,有的话等待执行,没有结束程序 -
查看当前进程的数量
def main(): t1 = threading.Thread(target=sing) t2 = threading.Thread(target=dance) t1.start() t2.start() while True: threading_length = len(threading.enumerate()) print('当前运行的程序有 {} 个'.format(threading_length)) if threading_length <= 1: break sleep(0.5)
3.2,线程资源冲突
3.2,线程锁
-
threading.Lock()
线程锁from threading import Thread, Lock # 导入Lock锁模块 x = 0 n = 100000 lock = Lock() def hello(n): global x for i in range(n): lock.acquire() # 线程上锁,其他线程不能访问当前线程使用的变量 x += 1 lock.release() # 线程解锁,变量x可以被其他线程访问 def hello1(n): global x for i in range(n): with lock: # 第二种方式:使用上下文管理,自动上锁解锁 a -= 1 i = Thread(target=hello, args=(n,)) d = Thread(target=hello1, args=(n,)) i.start() d.start() i.join() d.join()
-
threading.RLock()
可重入的锁from threading import Thread, RLock # 在同一个线程里面,可以连续调用多次acquire,但一定要注意acquire的次数要和release的次数相同 x = 0 n = 100000 lock = RLock() def hello(n): global x for i in range(n): lock.acquire() # 线程上锁,其他线程不能访问当前线程使用的变量 lock.acquire() lock.acquire() do_something() x += 1 lock.release() # 线程解锁,变量x可以被其他线程访问 lock.release() lock.release() def do_something(lock): lock.acquire() # do something lock.release() def hello1(n): global x for i in range(n): with lock: # 第二种方式:使用上下文管理,自动上锁解锁 do_something() a -= 1 i = Thread(target=hello, args=(n,)) d = Thread(target=hello1, args=(n,)) i.start() d.start() i.join() d.join()
3.3,死锁
如果A锁已经锁上,后面的程序想要用A锁就必须等待A锁释放
如果B锁已经锁上,后面的程序想要用B锁就必须等待B锁释放
如果a程序锁了A锁,b程序锁了B锁,a程序想要用B锁,等待。b程序想要用A锁,等待。就变成了死锁
下面的程序就说明了这个问题
3.4,condition锁
条件变量,用于复杂的线程间同步
- condition 的启动顺序很重要
- 在调用 with cond 之后才能调用 wait 方法或者 notify 方法
- condition 有两层锁,一把底层锁会在线程调用了 wait 方法的时候释放,上面的锁会在每次调用 wait 的时候分配一把锁放入 cond 的等待队列中,等待 notify 唤醒
from threading import Condition, Thread
class XiaoAi(Thread):
def __init__(self, cond):
super().__init__(name="小爱")
self.cond = cond
def run(self):
with self.cond:
self.cond.wait()
print('{} : 你好呀,天猫精灵'.format(self.name))
self.cond.notify()
self.cond.wait()
print('{} : 我们来对古诗把'.format(self.name))
self.cond.notify()
class TianMao(Thread):
def __init__(self, cond):
super().__init__(name="天猫精灵")
self.cond = cond
def run(self):
with self.cond:
print('{} : 小爱同学你好'.format(self.name))
self.cond.notify()
self.cond.wait()
print('{} : 好啊'.format(self.name))
self.cond.notify()
self.cond.wait()
print('{} : gun'.format(self.name))
if __name__ == '__main__':
cond = Condition()
xiaoai = XiaoAi(cond)
tianmao = TianMao(cond)
xiaoai.start()
tianmao.start()
3.5,信号量Semaphore
- 是用于控制进入数量的锁
- 文件,读,写一般只是用于一个线程写,读可以允许多个
- 做爬虫,控制并发数
class HtmlSpider(Thread):
def __init__(self, url, sem):
super().__init__()
self.url = url
self.sem = sem
def run(self):
time.sleep(2)
print('got html text success')
self.sem.release()
class UrlProducer(Thread):
def __init__(self, sem):
super().__init__()
self.sem = sem
def run(self):
for i in range(20):
self.sem.acquire()
html_thread = HtmlSpider('https://www.baidu.com/{}'.format(i), self.sem)
html_thread.start()
if __name__ == '__main__':
sem = Semaphore(3)
s_t = time.time()
url_producer = UrlProducer(sem)
url_producer.start()
url_producer.join()
3.6,多线程udp聊天器
-
思路:让接收和发送分别使用两条子线程,达到收发同步的目的
# !/usr/bin/env python # _*_ coding:utf-8 _*_ # author:满怀心 2019/8/3 10:26 """ # code is far away from bugs with the god animal protecting I love animals. They taste delicious. ┏┓ ┏┓ ┏┛┻━━━┛┻┓ ┃ ☃ ┃ ┃ ┳┛ ┗┳ ┃ ┃ ┻ ┃ ┗━┓ ┏━┛ ┃ ┗━━━┓ ┃ 神兽保佑 ┣┓ ┃ 永无BUG! ┏┛ ┗┓┓┏━┳┓┏┛ ┃┫┫ ┃┫┫ ┗┻┛ ┗┻┛ """ import argparse from socket import * import threading def send(udp_socket, dest_addr): while True: user_input = input('请输入想要发送的数据:\t') udp_socket.sendto(user_input.encode('utf-8'), dest_addr) def recv(udp_socket): while True: recv_data = udp_socket.recvfrom(1024) print('\n【{}】 -- 数据来自 {}:{}'.format(recv_data[0].decode('utf-8'), recv_data[1][0], recv_data[1][1])) def main(host, port): """主函数""" # 1. 创建udp套接字 udp_socket = socket(AF_INET, SOCK_DGRAM) # 2. 绑定端口 udp_socket.bind(('', 5566)) # 3. 确定对方的地址 dest_addr = (host, port) # 4. 发送数据+接收数据 t_send = threading.Thread(target=send, args=(udp_socket, dest_addr)) t_recv = threading.Thread(target=recv, args=(udp_socket,)) # 5. 执行多线程 t_send.start() t_recv.start() if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('--host', action='store', dest='host', required=False) parser.add_argument('--port', action='store', dest='port', type=int, required=False) given_args = parser.parse_args() host = given_args.host port = given_args.port main(host, port)