网络编程(3) - 多任务-线程

3.1,多任务的介绍

  1. 现实生活中很多事情是同时进行的,唱歌跳舞,手掌握方向盘,脚踩油门

  2. 多任务代码案例

    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()
    

    运行结果:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AuF3jFI5-1592062197135)(assets/.png)]

  3. 并发和并行

    1. 并发(运行的程序大于cpu核数):假的多任务,类似单片机的动态扫描,如果cpu只有1个核,他是这样运行的:先给第一个程序运行0.001秒,让第二个程序0.001秒,第三个0.001秒,像这样循环,达到肉眼看不到的速度,就变成了多任务

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SUycrLa2-1592062197139)(assets/.png)]

    2. 并行(运行的程序小于cpu核数):真正的多任务,一个核处理一个程序

3.2,线程(重点)

python的 thread 模块时比较底层的模块,python的 threading 模块时对thread做了一些包装的,可以更加方便的被使用

  1. 使用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 里面的任务,等到主线程执行结束,看看有没有子线程在执行,有的话等待执行,没有结束程序

  2. 查看当前进程的数量

     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)
    

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KtfNKzfT-1592062197141)(assets/.png)]

3.2,线程资源冲突

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FNkNqTbp-1592062197144)(assets/.png)]

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锁,等待。就变成了死锁

下面的程序就说明了这个问题

  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i0bPllf5-1592062197148)(assets/.png)]

3.4,condition锁

条件变量,用于复杂的线程间同步

  1. condition 的启动顺序很重要
  2. 在调用 with cond 之后才能调用 wait 方法或者 notify 方法
  3. 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

  1. 是用于控制进入数量的锁
  2. 文件,读,写一般只是用于一个线程写,读可以允许多个
  3. 做爬虫,控制并发数
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)
    

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值