多任务--线程

阅读内容

一、 多任务

二、线程

三、资源竞争

四、结论

五、案例

回到顶部

 

一、多任务

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.发送数据\n2.接受数据\n3.退出\n请选择:")
       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客户端

 

posted @ 2018-11-20 14:20  Mr。yang  阅读(187)  评论(0编辑  收藏  举报