• 多任务--线程


    阅读内容

    一、 多任务

    二、线程

    三、资源竞争

    四、结论

    五、案例

    回到顶部

    一、多任务

    1.目的

      多任务:在同一时间内执行多个任务,每个任务可以理解成现实生活中干的每个活。

    2.形式

      并发 :指的是任务数多余cpu核数,通过操作系统的各种任务调度算法,实现用多个任务“一起”执行

        并行 :指的是任务数小于等于cpu核数,即任务真的是一起执行的

    二、线程

    1.线程概念

      • 线程thread是一种实现多任务的手段
      • 线程是运行的程序中一个执行流程 <分支/线索>
      • 一个程序中默认存在一个线程 主线程mainthread, 新建的线程称为子线程
      • 编写完程序后, 在这个程序中的所有代码都是主程序

    2.创建线程

    1.创建对象 : 对象 = threading.Thread(target=入口, args=(), kwargs={})  

          # args 传入的是位置参数  kwargs传入的关键字参数  args 后面 必须是元组形式的 字符

    2.启动线程: 对象.start()

    3.线程中的方法

      • 查看存货的线程列表 :  threading.enumerate()
      • 阻塞等待子线程 : 对象.join()
      • 查看当前线程 : threading.current_thread()
      • 查看线程名称:  threading.name()

    4.线程中的注意点

    #!/usr/bin/env python
    # _*_ coding:utf-8 _*_
    # Author:Mr.yang
    import threading
    import time
    
    def sing(singer, address):
        """唱歌"""
        for i in range(2):
            print("%s正在%s唱 sing..." % (singer, address))
            time.sleep(1)
    
    def dance(dancer, address):
        """跳舞"""
        for i in range(2):
            print("%s正在%s跳 dance..." % (dancer, address))
            print(threading.enumerate())
            time.sleep(1)
    
    
    
    def main():
        """主线程"""
        # 创建子线程 先创建出来threading.Thread 类的对象 .start()
        # target 目标 指定子线程运行的入口函数 ;
        # args 用来指定子线程函数运行所需的 位置参数的元组
        # kwargs 是指定子线程运行所需的 关键字参数的字典
        sing_thd = threading.Thread(target=sing, args=('玲花',), kwargs={'address': '鸟巢'})
        sing_thd.start()
    
        dance_thd = threading.Thread(target=dance, args=('mike',), kwargs={'address': '月亮之上'})
        dance_thd.start()
    
        print("这是主线程最后的代码")
    
        # 结论 : 主线程创建出了子线程 主线程默认情况下不会直接退出 而是等待子线程结束后在一起退出
        # 原因 : 主线程有义务去挥手子线程相应的资源
    
    if __name__ == '__main__':
        main()
    
    
    ####  执行结果 ####
    玲花正在鸟巢唱 sing...
    mike正在月亮之上跳 dance...
    [<_MainThread(MainThread, started 1916)>, <Thread(Thread-1, started 10688)>, <Thread(Thread-2, started 8036)>]
    这是主线程最后的代码
    mike正在月亮之上跳 dance...
    玲花正在鸟巢唱 sing...
    [<_MainThread(MainThread, stopped 1916)>, <Thread(Thread-1, started 10688)>, <Thread(Thread-2, started 8036)>]
    主进程等待子进程执行完成
    #!/usr/bin/env python
    # _*_ coding:utf-8 _*_
    # Author:Mr.yang
    import threading
    import time
    
    # threading.current_thread().name 打印当前线程名
    def task():
        for i in range(3):
            print("当前线程:", threading.current_thread())
            time.sleep(1)
    
    
    if __name__ == '__main__':
        for _ in range(5):
            sub_thread = threading.Thread(target=task)
            sub_thread.start()
            print(threading.enumerate())
    #### 运行结果 ####
    当前线程: <Thread(Thread-1, started 2776)>
    当前线程: <Thread(Thread-2, started 5684)>
    当前线程: <Thread(Thread-3, started 8056)>
    当前线程: <Thread(Thread-4, started 7700)>
    当前线程: <Thread(Thread-5, started 9964)>
    当前线程: <Thread(Thread-5, started 9964)>
    当前线程: <Thread(Thread-4, started 7700)>
    当前线程: <Thread(Thread-3, started 8056)>
    当前线程: <Thread(Thread-2, started 5684)>
    当前线程: <Thread(Thread-1, started 2776)>
    当前线程: <Thread(Thread-1, started 2776)>
    当前线程: <Thread(Thread-3, started 8056)>
    当前线程: <Thread(Thread-5, started 9964)>
    当前线程: <Thread(Thread-4, started 7700)>
    当前线程: <Thread(Thread-2, started 5684)>
    证明线程执行无序
    #!/usr/bin/env python
    # _*_ coding:utf-8 _*_
    # Author:Mr.yang
    import threading
    import time
    
    class MyThread(threading.Thread):
        """子类 将Thread类和线程函数封装在一起 提高代码的可维护性"""
        # 吐过需要在子类中运行某个代码  需要实现run方法
        def run(self):
            """子线程需要运行的代码 一旦子线程启动运行 自动调用该方法"""
            print("这是子线程 %s" % (threading.enumerate()))
            self.sing()
    
        def sing(self):
            for i in range(5):
                print("这是子线程")
                time.sleep(1)
    
    class main():
        """对象 = threading.Thread; 对象.start()"""
        mt = MyThread()
        mt.start() # 创建并启动子线程 调用run方法
        # mt.run() # 这么编写代码 会执行run方法的代码
    
        for i in range(5):
            print("这是主线程")
            time.sleep(1)
    
    
    if __name__ == '__main__':
        main()
    自定义线程类
    #!/usr/bin/env python
    # _*_ coding:utf-8 _*_
    # Author:Mr.yang
    import threading
    import time
    
    def sing(singer, address):
        """唱歌"""
        for i in range(30):
            print("%s正在%s唱 sing..." % (singer, address))
            time.sleep(1)
    
    def dance(dancer, address):
        """跳舞"""
        for i in range(50):
            print("%s正在%s跳 dance..." % (dancer, address))
            time.sleep(1)
    
    
    def main():
        """主线程"""
        # 创建子线程 先创建出来threading.Thread 类的对象 .start()
        # target 目标 指定子线程运行的入口函数 ;
        # args 用来指定子线程函数运行所需的 位置参数的元组
        # kwargs 是指定子线程运行所需的 关键字参数的字典
        sing_thd = threading.Thread(target=sing, args=('玲花',), kwargs={'address': '鸟巢'})
        sing_thd.daemon = True
        sing_thd.start()
    
        dance_thd = threading.Thread(target=dance, args=('mike',), kwargs={'address': '月亮之上'}, daemon=True)
        # dance_thd.setDaemon(True)
        dance_thd.start()
    
        # 主线程结束后 子线程也跟着结束
        print("main...")
        time.sleep(2)
    
    # 设置守护线程方法
    # 1. 对象.setDaemon(True) # 在start之前调用
    # 2. 对象.daemon = True # 在start之前调用
    # 3. Thread(...,daemon = True) 这种方法只适用于python3
    
    if __name__ == '__main__':
        main()
    
    
    #### 执行结果 ####
    玲花正在鸟巢唱 sing...
    mike正在月亮之上跳 dance...
    main...
    玲花正在鸟巢唱 sing...
    mike正在月亮之上跳 dance...
    守护线程

    三、资源竞争

    1.验证子线程是否可以修改全局变量

    #!/usr/bin/env python
    # _*_ coding:utf-8 _*_
    # Author:Mr.yang
    import threading
    
    g_number = 100
    
    def update_number():
        """子线程  修改  全局变量"""
        global g_number
    
        g_number += 1
        print("全局变量:%s" % g_number)
    
    def main():
        # 创建一个子线程  用来修改全局变量的值
        thd= threading.Thread(target=update_number)
        thd.start()
    
        # 阻塞等待子线程运行完成
        thd.join()
        # 打印
        print(g_number)
    
        # 结论: 同一个进程内部的多个线程 是共享全局变量的
    
    if __name__ == '__main__':
        main()
    
    #### 执行结果 ####
    全局变量:101
    101
    修改全局变量

    2.子线程修改全局变量的存在的问题

    问题 : 同一个进程内部的多个线程 是共享全局变量的, 导致同时修改全局资源时产生混乱的现象-资源竞争 数据竞争

    #!/usr/bin/env python
    # _*_ coding:utf-8 _*_
    # Author:Mr.yang
    import threading
    
    g_number = 0
    
    def update_number1():
        """子线程  修改  全局变量"""
        global g_number
        for i in range(1000000):
            g_number += 1
    
    def update_number2():
        """子线程  修改  全局变量"""
        global g_number
        for i in range(10000000):
            g_number += 1
    
    def main():
        # 创建一个子线程  用来修改全局变量的值
        thd1= threading.Thread(target=update_number1)
        thd1.start()
        thd2= threading.Thread(target=update_number1)
        thd2.start()
    
        # 阻塞等待子线程运行完成
        thd1.join()
        thd2.join()
        # 打印
        print(g_number)
    
        # 结论: 同一个进程内部的多个线程 是共享全局变量的
        # 导致同时修改全局资源时产生混乱的现象-资源竞争 数据竞争
    
    if __name__ == '__main__':
        main()
    
    #### 执行结果 ####
    1273332
    问题

    3.解决问题的方法------互斥锁

    #!/usr/bin/env python
    # _*_ coding:utf-8 _*_
    # Author:Mr.yang
    import threading
    
    g_number = 0
    
    def update_number1(lock):
        """子线程  修改  全局变量"""
        global g_number
        for i in range(1000000):
            # 修改全局变量之前 应该先申请锁; 如果所已经被别人锁定了 那当前任务就会阻塞等待直到对方释放锁
            lock.acquire()
            g_number += 1
            # 修改完成之后 应该释放互斥锁
            lock.release()
    
    def update_number2(lock):
        """子线程  修改  全局变量"""
        global g_number
        for i in range(1000000):
            lock.acquire()
            g_number += 1
            lock.release()
    
    def main():
        # 创建互斥锁对象   Lock好比一个类 所以后面要加括号
        lock = threading.Lock()
        # 创建一个子线程  用来修改全局变量的值
        thd1= threading.Thread(target=update_number1, args=(lock,))
        thd1.start()
        thd2= threading.Thread(target=update_number1, args=(lock,))
        thd2.start()
    
        # 阻塞等待子线程运行完成
        thd1.join()
        thd2.join()
        # 打印
        print(g_number)
    
        # 结论: 同一个进程内部的多个线程 是共享全局变量的
        # 导致同时修改全局资源时产生混乱的现象-资源竞争 数据竞争
    
    if __name__ == '__main__':
        main()
    
    #### 执行结果 ####
    200000
    互斥锁

    4.互斥锁存在问题------死锁

    # #!/usr/bin/env python
    # # _*_ coding:utf-8 _*_
    # # Author:Mr.yang
    # 死锁
    from threading import Thread,Lock
    import time
    
    mutexA = Lock()
    mutexB = Lock()
    
    class MyThread(Thread):
        def run(self):
            self.f1()
            self.f2()
    
        def f1(self):
            mutexA.acquire()
            print('%s 拿到了A锁' % self.name)
    
            mutexB.acquire()
            print('%s 拿到了B锁' % self.name)
            mutexB.release()
            mutexA.release()
    
        def f2(self):
            mutexB.acquire()
            print('%s 拿到了B锁' % self.name)
            time.sleep(0.01)
            mutexA.acquire()
            print('%s 拿到了A锁' % self.name)
            mutexB.release()
            mutexA.release()
    
    if __name__ == '__main__':
        for i in range(10):
            t=MyThread()
            t.start()
    # # '''
    # # 打印结果:
    # # Thread-1 拿到了A锁
    # # Thread-1 拿到了B锁
    # # Thread-1 拿到了B锁
    # # Thread-2 拿到了A锁
    # # '''
    死锁

    5.正确释放锁的方法

    #!/usr/bin/env python
    # _*_ coding:utf-8 _*_
    # Author:Mr.yang
    import threading
    
    g_number = 0
    
    def update_number1(lock):
        """子线程  修改  全局变量"""
        global g_number
        for i in range(100000):
            # 修改全局变量之前 应该先申请锁; 如果所已经被别人锁定了 那当前任务就会阻塞等待直到对方释放锁
            with lock:
                g_number += 1
                # 修改完成之后 应该释放互斥锁
    
    
    def update_number2(lock):
        """子线程  修改  全局变量"""
        global g_number
        for i in range(100000):
            with lock:
                g_number += 1
    
    def main():
        # 创建互斥锁对象   Lock好比一个类 所以后面要加括号
        lock = threading.Lock()
        # 创建一个子线程  用来修改全局变量的值
        thd1= threading.Thread(target=update_number1, args=(lock,))
        thd1.start()
        thd2= threading.Thread(target=update_number1, args=(lock,))
        thd2.start()
    
        # 阻塞等待子线程运行完成
        thd1.join()
        thd2.join()
        # 打印
        print(g_number)
    
        # 结论: 同一个进程内部的多个线程 是共享全局变量的
        # 导致同时修改全局资源时产生混乱的现象-资源竞争 数据竞争
    
    if __name__ == '__main__':
        main()
    
    #### 执行结果 ####
    200000
    方法

    四、结论

    1. 多线程顺序是无序的 随机执行

    2. 主线程默认会等待子线程全部结束 才退出

    3. 主线程会等待所有的子线程结束后才结束,如果需要可以设置守护主线程

    4. 多个子线程执行的 过程中 会产生资源竞争问题 ---- 解决方法使用互斥锁

    5. 互斥锁 

       优点 : 能够保证代码的正确执行 不会产生资源竞争问题

       缺点 : 降低了多任务执行的效率  容易造成死锁问题 deadlock

    五、案例

    #!/usr/bin/env python
    # _*_ coding:utf-8 _*_
    # Author:Mr.yang
    import socket
    import threading
    
    def send_message(udp_socket):
        """发送消息"""
        # 需要用户数输入数据 IP 端口
        data = input("数据:")
        IP = input("IP:")
        port = int(input("端口:"))
        udp_socket.sendto(data.encode(), (IP, port))
    
    
    def recv_message(udp_socket):
        """接受消息"""
        while True:
            res_data, addr = udp_socket.recvfrom(1024)
            print("用户%s传入的数据为:%s" % (str(addr), res_data.decode('gbk')))
    
    
    def main():
    
        # 1.创建UDP套接字
        udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        udp_socket.bind(('', 9999))
    
        # 1.1 创建子线程
        recv_thd = threading.Thread(target=recv_message, args=(udp_socket,), daemon=True)
        recv_thd.start()
        # 2.开始循环接受用户输入,1:发送 2:接受 3:退出; 其他重新输入
        while True:
           op = input("1.发送数据
    2.接受数据
    3.退出
    请选择:")
           if op == '1':
               send_message(udp_socket)
           elif op == '2':
               recv_message(udp_socket)
           elif op == '3':
               print('buy')
               break
           else:
               print("输出有误,重新输入")
    
        # 3.退出循环才关闭
        udp_socket.close()
    if __name__ == '__main__':
        main()
    多任务的UDP聊天器
    #!/usr/bin/env python
    # _*_ coding:utf-8 _*_
    # Author:Mr.yang
    import socket
    import threading
    
    def clent_request(client_socket):
        """处理 用户客户端请求"""
        while True:
            # 5.使用分机进行深入的交流 - echo 回射服务器
            recv_data = client_socket.recv(1024)
            print("接受数据:%s" % recv_data.decode('gbk'))
            client_socket.send(recv_data.decode('gbk').encode())
            if not  recv_data:
                print("客户端下线了")
                break
    
            # 6.分机挂机
        client_socket.close()
    
    if __name__ == '__main__':
    
        # 1.总机 - 创建TCP套接字<服务器套接字 监听套接字>
        server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    
    # 2.固定号码 - 固定端口
        server_socket.bind(('', 8888))
    
        # 3.安装客户服务系统 - 主动 -> 被动监听模式
        server_socket.listen(128)
    
        while True:
            # 4.从等待服务区取出一个客户端用以服务 转接到分机 - 接受链接
            # (<socket.socket 和客户端关联起来的套接字对象), raddr=('172.17.99.129', 53614)>, ('172.17.99.129', 53614))
            client_socket, client_addr = server_socket.accept()
            print("接受来自%s的数据请求" % str(client_socket))
    
            thd = threading.Thread(target=clent_request, args=(client_socket,))
            thd.start()
    
    
        # 7.主机挂机
        server_socket.close()
    多任务TCP服务端
    #!/usr/bin/env python
    # _*_ coding:utf-8 _*_
    # Author:Mr.yang
    import socket
    
    # 1.买个电话 -- 创建TCP套接字   参数是 地址协议版本 套接字类型
    tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    # 2.拨号 创建和服务器的链接
    IP = input("IP地址:")
    port = int(input("Port:"))
    tcp_socket.connect((IP, port))
    
    # 3.发送数据
    while True:
        choose = input(">>>:")
        if choose == 'q':
            break
        tcp_socket.send(choose.encode())
    
        # 4.接受数据 阻塞等待数据 recv返回值一般情况下 就是对方发送的数据; 如果对方断开了链接 返回值就是 ''
        recv_data = tcp_socket.recv(1024)
        if not recv_data:
            print("对方断开链接")
        else:
            print(recv_data.decode('gbk'))
    
    # 5.关闭套接字
    tcp_socket.close()
    TCP客户端
  • 相关阅读:
    第二阶段冲刺之站立会议5
    第二阶段冲刺之站立会议4
    第二阶段冲刺之站立会议3
    “加减乘除”使用说明
    第二阶段冲刺之站立会议2
    Kali学得好,监狱蹲到老,最新版Kali 2020
    【Linux】Centos7密码登录失败锁定设置_20200313
    【Linux】如何设置Linux开机 ,默认进入图形界面或命令行界面?
    【Linux】使用 PXE+Kickstart 无人值守批量安装系统
    【Linux】Linux修改openfile和max user processes?
  • 原文地址:https://www.cnblogs.com/Mryang123/p/9988733.html
Copyright © 2020-2023  润新知