第8章 python多任务-线程

1、多任务

有很多的场景中的事情是同时进行的,比如开车的时候 手和脚共同来驾驶汽车,再比如唱歌跳舞也是同时进行的

1.1 程序中模拟多任务

import time


def sing():
    for i in range(3):
        print('I am singing...%d' % i)
        time.sleep(1)


def dance():
    for i in range(3):
        print('I am dancing...%d' % i)
        time.sleep(1)


if __name__ == '__main__':
    sing()
    dance()

上面代码返回结果:
I am singing…0
I am singing…1
I am singing…2
I am dancing…0
I am dancing…1
I am dancing…2
通过返回结果可知,singing执行完成,dance才会开始

1.2 多任务的理解

并行:真的多任务 cpu大于当前执行的任务
并发:假的多任务 cpu小于当前执行的任务
线程完成多任务

threading.Thread() 有两个典型参数:
target : 指定 这个线程去哪个函数里面去执行代码
args: 指定将来调用 函数的时候 传递什么数据过去
args参数指定的一定是一个元组类型

import time
import threading


def sing():
    for i in range(3):
        print('I am singing...%d' % i)
        time.sleep(1)


def dance():
    for i in range(3):
        print('I am dancing...%d' % i)
        time.sleep(1)


if __name__ == '__main__':
    # sing()
    # dance()
    t1 = threading.Thread(target=sing)
    t2 = threading.Thread(target=dance)
    t1.start()
    t2.start()  # 主线程会等子线程执行完之后,主线程才会结束

上面代码返回:
I am singing…0
I am dancing…0
I am dancing…1
I am singing…1
I am dancing…2
I am singing…2
说明:通过上面返回结果可知,singing 和 dance 同时进行

1.3 其它特性

查看线程数量
threading.enumerate() 查看当前线程的数量
验证子线程的执行与创建
当调用Thread的时候,不会创建线程。
当调用Thread创建出来的实例对象的start方法的时候,才会创建线程以及开始运行这个线程。

import time
import threading


def sing():
    for i in range(3):
        print('I am singing...%d' % i)
        # time.sleep(1)


def dance():
    for i in range(3):
        print('I am dancing...%d' % i)
        # time.sleep(1)


if __name__ == '__main__':
    # sing()
    # dance()
    t1 = threading.Thread(target=sing)
    t2 = threading.Thread(target=dance)
    t1.start()
    t2.start()  # 主线程会等子线程执行结束之后,主线程继续执行
    print(threading.enumerate())

运行上面程序返回结果:
I am singing…0
I am singing…1I am dancing…0
[<_MainThread(MainThread, started 8496)>, <Thread(Thread-1, started 2276)>, <Thread(Thread-2, started 3648)>]
I am singing…2
I am dancing…1
I am dancing…2
通过结果可知,一个主线程_MainThread,两个子线程:Thread-1 Thread-2

1.4 守护线程 setDaemon() 和 jion()

setDaemon() 守护线程 不会等子线程结束
jion() 等子线程执行结束,主线程继续执行

import time
import threading


def demo():
    for i in range(3):
        print("holle world")
        time.sleep(0.1)


def main():
    t = threading.Thread(target=demo)
    # 守护线程  不会等子线程结束
    t.setDaemon(True)

    t.start()

    #等子线程执行结束,主线程继续执行
    t.join()

    print('1')

t.setDaemon(True) #不等子线程打印三次 holle world ,主线程结束
在这里插入图片描述
t.join() 等子线程结束,主线程继续执行
在这里插入图片描述

1.5 验证子线程的创建与执行

import time
import threading


def demo():
    for i in range(5):
        print('执行demo函数')
        time.sleep(1)


def main():
    print('1',threading.enumerate())
    t = threading.Thread(target=demo)
    print('2',threading.enumerate())
    
    #创建子线程,开始执行
    t.start()
    print('3',threading.enumerate())


if __name__ == '__main__':
    main()

返回结果可以看到,1、2是主线程在运行,直到 t.start() 执行后才会看到主线程和子线程
在这里插入图片描述

2、继承Thread类创建线程

run() 函数是在主函数 a.start() 时候自动调用的

import time
import threading


class A(threading.Thread):
    def run(self):
        for i in range(5):
            self.demo()

    def demo(self):
        print('我是demo')


def main():
    a = A()
    a.start()


if __name__ == '__main__':
    main()

在这里插入图片描述

3 线程间通信

3.1 什么是线程间通信呢?

多个线程处理同一资源,但是任务不同

3.2 为什么要通信?

如果各个线程之间各干各的,确实不需要通信,这样的代码也十分的简单。但这一般是不可能的,至少线程要和主线程进行通信,不然计算结果等内容无法取回。而实际情况中要复杂的多,多个线程间需要交换数据,才能得到正确的执行结果。

3.3 多线程共享全局变量(线程间通信)

修改全局变量一定需要加global吗?
答案是:在一个函数中,对全局变量进行修改的时候,是否要加global要看是否对全局变量的指向进行了修改,如果修改了指向,那么必须使用global,仅仅是修改了指向的空间中的数据,此时不用必须使用global
看下面例子

import threading
import time

num = 100
lis = [2, 3]


def demo1():
    # global 让 demo1 和 demo2 访问的是同一个 num
    global num
    #global lis
    num += 50
    #而list类型变量 lis 通过“append”函数添加对象就不用声明全局,因为lis的指向没有改变
    lis.append(4)
    print('demo1.num----%d' % num)
    print('demo1.lis----{}'.format(lis))

    #如果 lis 以“+”的方式添加元素,那么因为指向跟append不同,而必须用“global”声明全局变量,否则程序报错
    # lis = lis + [7]
    # print('demo1.lis----{}'.format(lis))


def demo2():
    global lis
    lis.append(5)
    print('demo2.num----%d' % num)
    print('demo2.lis----{}'.format(lis))


def main():
    # global lis
    t1 = threading.Thread(target=demo1)
    t2 = threading.Thread(target=demo2)

    t1.start()
    time.sleep(0.1)

    t2.start()
    time.sleep(0.1)
    print('main-----%d' % num)


if __name__ == '__main__':
    main()

上面代码返回值
在这里插入图片描述

3.4多线程参数-args

threading.Thread(target=test, args=(num,))

import time
import threading

lst = [11, 22]
print('lst---:',id(lst))


def demo1(lst):
    lst.append(33)
    print('demo1.lst----:', lst,id(lst))


def demo2(lst):
    print('demo2.lst----:', lst,id(lst))


def main():
    t1 = threading.Thread(target=demo1, args=(lst, ))
    t2 = threading.Thread(target=demo2, args=(lst, ))
    # print(t1.__dict__)

    t1.start()
    time.sleep(0.1)
    t2.start()
    time.sleep(0.1)
    print('mian.lst----:', lst,id(lst))


if __name__ == '__main__':
    main()

返回值:
在这里插入图片描述

4、共享全局变量资源竞争

多线程是为了提高效率,让程序并发执行,但并发执行会引起资源竞争,看下面的程序,当nums参数很小时10万级不会出问题,但百万级时

4.1 一个资源竞争导致结果错误的例子

import threading
import time

num = 0


def demo1(nums):
    global num
    for i in range(nums):
        num += 1
    print('demo1-----num:{}'.format(num))


def demo2(nums):
    global num
    for i in range(nums):
        num += 1
    print('demo2-----num:{} \n'.format(num))


def main():
    t1 = threading.Thread(target=demo1, args=(1000,))
    t2 = threading.Thread(target=demo2, args=(1000,))

    t1.start()
    t2.start()

    print('main-----num:{}'.format(num))


if __name__ == '__main__':
    main()

当nums数字不大时,因为处理器处理速度快,不会出问题,当数字大了,就会出现demo1 和 demo2 资源竞争的问题。
nums = 10000时,返回值如下:
在这里插入图片描述
当nums= 100000时,返回值就会错乱,因为demo1 demo2 和主函数 是根据cpu分时间片运行的,变量num得不到很好的控制,要解决这个问题需要用到互斥锁
在这里插入图片描述

4.1.1 python字节码

'''
python 字节码  --> python 虚拟机来执行python字节码
'''
import dis

def add_num(a):
    a += 1

print(dis.dis(add_num))

上面执行结果:能看出来a被加载,a加1,和返回a的过程
在这里插入图片描述

4.2 互斥锁

一个线程写入,一个线程读取,没问题,如果两个线程都写入呢?
当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制

某个线程要更改共享数据时,先将其锁定,此时资源的状态为"锁定",其他线程不能改变,只到该线程释放资源,将资源的状态变成"非锁定",其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。

mutex = threading.lock() 创建一个互斥锁对象
mutex.acquire() 上锁
mutex.release() 解锁

import threading
import time

num = 0
# 创建一个互斥锁对象,默认是没有上锁的
mutex = threading.Lock()


def demo1(nums):
    global num
    #上锁共享变量 num
    mutex.acquire()
    for i in range(nums):
        num += 1
    #解锁共享变量 num
    mutex.release()
    print('demo1---num:', num)


def demo2(nums):
    global num
    #上锁共享变量 num
    mutex.acquire()
    for i in range(nums):
        num += 1
    #解锁共享变量 num
    mutex.release()
    print('demo2---num:', num)


def main():
    t1 = threading.Thread(target=demo1, args=(1000000,))
    t2 = threading.Thread(target=demo2, args=(1000000,))

    t1.start()
    t2.start()
    
	#主进程睡眠1秒钟,给demo1 和 demo2 执行时间
	#还可以用 t1.join()  t2.join() 函数,等待子函数执行完之后,再执行主函数
    time.sleep(1)
    print('main-----num:%d' % num)


if __name__ == '__main__':
    main()

返回值:
在这里插入图片描述
重入锁:mutex = threading.Rlock()
重入锁可以嵌套,但上锁与解锁必须成对出现

5 线程同步

如果想让多线程实现下面信息问答同步需要用到 threading.condition 方法函数
天猫精灵:小爱同学
小爱同学:在
天猫精灵:现在几点了?
小爱同学:你猜猜现在几点了

6 多任务版udp聊天

1 创建套接字
2 绑定本地信息
3 获取对方IP和端口
4 发送、接收数据
5 创建两个线程,去执行功能

import threading
import socket


def recv_msg(udp_socket):
    while True:
        recv_data = udp_socket.recvfrom(1024)
        print('收到%s发来的消息:%s' % (recv_data[1], recv_data[0].decode('gbk')))
        # print(recv_data)


def send_msg(udp_socket, dest_ip, dest_port):
    while True:
        send_data = input('清输入发送的内容:')
        udp_socket.sendto(send_data.encode('gbk'), (dest_ip, dest_port))


def main():
    udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    # bind_str = ("",8080)
    udp_socket.bind(("", 8899))

    # 对方ip 和 端口
    dest_ip = '192.168.2.119'
    dest_port = 8090

    t_recv = threading.Thread(target=recv_msg, args=(udp_socket,))
    t_send = threading.Thread(target=send_msg, args=(udp_socket, dest_ip, dest_port))

    t_recv.start()
    t_send.start()


if __name__ == '__main__':
    main()

网络调试助手网络设置为UDP
在这里插入图片描述

发布了31 篇原创文章 · 获赞 0 · 访问量 343

猜你喜欢

转载自blog.csdn.net/weixin_38027481/article/details/103933857