多任务复习

1.什么是多任务

简单的说,就是同时可以运行多个任务。

2.多任务执行原理

操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再切换到任务3,执行0.01秒……这样反复执行下去。表面上看,每个任务都是交替执行的,但是,由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样。

3.线程

  1. 什么是线程
    线程就是程序在执行代码的那个执行流
  2. 使用threading模块
    1. 单线程执行
      import time
      
      def say_sorry():
      	print('我错了,我能吃饭么?')
      	time.sleep(1)
      
      for i in range(5):
      	say_sorry()
      
    2. 多线程执行
      import threading
      import time
      
      def say_sorry():
      	print('我错了,我能吃饭了么?')
      	time.sleep(1)
      
      for i in range(5):
      	t = threading.Thread(target=say_sorry)
      	t.start()  # 启动线程,即让线程开始执行
      
    3. 说明
      1. 可以看出多线程并发操作,浪费的时间短
      2. 当调用start()时,才会真正的创建线程,并且开始执行
    4. 查看线程的数量
      使用threading.enumerate()能够得到当前程序在运行时,所有的线程信息,以列表的方式返回
    5. 总结:
      1. 一个多任务线程中最少有一个线程(主线程)

4.创建线程传递参数

  1. 传递多个参数
    from threading import Thread
    import time
    
    def work1(num1, num2):
        print("----in work1--num1=%d,num2=%d-" % (num1, num2))
    
    
    def work2(num1, num2, num3):
        print("----in work1--num1=%d,num2=%d,num3=%d-" % (num1, num2, num3))
    
    t1 = Thread(target=work1, args=(11, 22))
    t1.start()
    
    t2 = Thread(target=work2, args=(33, 44, 55))
    t2.start()
    
  2. 传递命名参数
    from threading import Thread
    import time
    
    def work1(num1, num2, m):
        print("----in work1--num1=%d,num2=%d,m=%d-" % (num1, num2, m))
    
    
    def work2(num1, num2, num3, n):
        print("----in work1--num1=%d,num2=%d,num3=%d,n=%d-" % (num1, num2, num3, n))
    
    t1 = Thread(target=work1, args=(11, 22), kwargs={"m": 100})
    t1.start()
    
    t2 = Thread(target=work2, args=(33, 44, 55), kwargs={"n": 200})
    t2.start()
    

5. 案例:同时收发数据的UDP聊天程序

import socket
import threading


def send_msg(udp_socket):
    """获取键盘数据,并将其发送给对方"""
    while True:

        print("1: 发送数据")
        print("2: 退出程序")
        op = input("请输入操作序号:")
        if op == "1":
            # 1. 输入对方的ip地址
            dest_ip = input("\n请输入对方的ip地址:")
            # 2. 输入对方的port
            dest_port = int(input("\n请输入对方的port:"))
            while True:
                # 3. 从键盘输入数据
                msg = input("\n请输入要发送的数据:")
                if msg:
                    # 4. 发送数据
                    udp_socket.sendto(msg.encode("utf-8"), (dest_ip, dest_port))
                else:
                    # 要是没有输入内容则认为是要重新输入ip、port
                    break
        elif op == "2":
            break

    udp_socket.close()


def recv_msg(udp_socket):
    """接收数据并显示"""
    while True:
        try:
            # 1. 接收数据
            recv_msg = udp_socket.recvfrom(1024)
        except:
            break
        else:
            # 2. 解码
            recv_ip = recv_msg[1]
            recv_msg = recv_msg[0].decode("utf-8")
            # 3. 显示接收到的数据
            print(">>>%s:%s" % (str(recv_ip), recv_msg))


def main():
    # 1. 创建套接字
    udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    # 2. 绑定本地信息
    udp_socket.bind(("", 7890))

    # 3. 创建一个新的线程,用来接收数据
    udp_r = threading.Thread(target=recv_msg, args=(udp_socket,))

    # 4. 创建一个新的线程,用来发送数据
    udp_s = threading.Thread(target=send_msg, args=(udp_socket,))

    # 5. 运行创建的子线程
    udp_r.start()
    udp_s.start()

if __name__ == "__main__":
    main()

总结:

  1. udp是全双工的,可以同时收发数据
  2. 创建一个udp套接字可以传递多个线程同时使用,一个收数据,一个发数据
  3. 如果某个线程将套接字close,那么意味着这个udp套接字不能在使用了

6.创建线程的另外一种方式

  1. 定义一个新的类,继承Thread类
  2. 在这个类中实现run方法
  3. 在run方法中写如要执行的代码
  4. 当使用这类创建一个对象后,调用对象的start方法就可以让这个线程执行,且会自动执行run方法的代码
import threading
import time

class MyThread(threading.Thread):
    def run(self):
        for i in range(3):
            msg = "I'm "+ self.name + ' @ '+str(i) #  name属性中保存的是当前线程的名字
            print(msg)
            time.sleep(1)


if __name__ == '__main__':
    t = MyThread()
    t.start()

7.并发TCP服务器

import socket
import threading


class HandleData(threading.Thread):
    def __init__(self, client_socket):
        super().__init__()
        self.client_socket = client_socket

    def run(self):
        # 接收/发送数据
        while True:
            recv_content = self.client_socket.recv(1024)
            if len(recv_content) != 0:
                print(recv_content)
                self.client_socket.send(recv_content)
            else:
                self.client_socket.close()
                break

    def __del__(self):
        self.client_socket.close()



class TCPServer(threading.Thread):
    def __init__(self, port):
        # 调用父类的初始化方法
        # threading.Thread.__init__(self)
        super().__init__()

        # 创建套接字
        self.server_s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

        # 绑定本地信息
        self.server_s.bind(("", port))

        # 将套接字由默认的主动链接模式改为被动模式(监听模块)
        self.server_s.listen(128)


    def run(self):
        # 等待客户端进行链接
        while True:
            new_s, client_info = self.server_s.accept()
            print(client_info)

            # t = HandleData(new_s)
            # t.start()
            HandleData(new_s).start()

    def __del__(self):
        # 关闭套接字
        self.server_s.close()


def main():
    tcp_server = TCPServer(7788)  # 7788表示TCP要绑定的端口
    tcp_server.start()

if __name__ == '__main__':
    main()

在这里插入图片描述

8.全局变量

  1. 在一个进程中所有线程共享全局变量,很方便在多个线程间共享数据
  2. 缺点是:线程是对全局变量随意篡改可能造成多线程之间对全局变量的混乱(即线程非安全)
  3. 如果多个线程对同一个全局变量操作,会出现资源竞争(解决方法互斥锁)

9.互斥锁

  1. 为什么使用互斥锁?
    防止多个线程出现资源竞争的问题
  2. 状态
    锁定 或者 非锁定
  3. 使用互斥锁
    1. 创建锁 :mutex = threading.Lock()
    2. 锁定:mutex.acquire()
    3. 释放:mutex.release()
  4. 案例实现
    完成2个线程对同一个变量各加100万次操作
    import threading
    import time
    
    g_num = 0
    
    def test1(num):
        global g_num
        for i in range(num):
            mutex.acquire()  # 上锁
            g_num += 1
            mutex.release()  # 解锁
    
        print("---test1---g_num=%d"%g_num)
    
    def test2(num):
        global g_num
        for i in range(num):
            mutex.acquire()  # 上锁
            g_num += 1
            mutex.release()  # 解锁
    
        print("---test2---g_num=%d"%g_num)
    
    # 创建一个互斥锁
    # 默认是未上锁的状态
    mutex = threading.Lock()
    
    # 创建2个线程,让他们各自对g_num加1000000次
    p1 = threading.Thread(target=test1, args=(1000000,))
    p1.start()
    
    p2 = threading.Thread(target=test2, args=(1000000,))
    p2.start()
    
    # 等待计算完成
    while len(threading.enumerate()) != 1:
        time.sleep(1)
    
    print("2个线程对同一个全局变量操作之后的最终结果是:%s" % g_num)
    
  5. 好处
    确保了某段关键代码同时只能由一个线程从头到尾完整地执行
  6. 坏处
    1. 阻止了线程并发执行,包含锁的某段代码实现上只能以单线程模式执行,效率就大大地下降了
    2. 由于可以存着多个锁,不同的线程有不同的锁,并试图获取对方持有的锁时,可能造成死锁

10.进程

  1. 什么是程序?
    就是一堆代码的称呼
  2. 什么是程序?
    一个程序运行起来后,代码+用到的资源 称之为进程,他是操作系统分配资源的基本单位
  3. 实现多任务的方式
    1. 创建Process对象
    2. 基础Process类,创建自己的对象,实现run重写
    3. 使用进程池
  4. 创建进程方式1
    from multiprocessing import Process
    import time
     
    def test():
        """子进程单独执行的代码"""
        while True:
            print('---test---')
            time.sleep(1)
     
    if __name__ == '__main__':
        p=Process(target=test)
        p.start()
        # 主进程单独执行的代码
        while True:
            print('---main---')
            time.sleep(1)
    
  5. 进程PID(进程号,一个程序一个进程号)
    from multiprocessing import Process
    import os
    import time
    
    def run_proc():
        """子进程要执行的代码"""
        print('子进程运行中,pid=%d...' % os.getpid())  # os.getpid获取当前进程的进程号
        print('子进程将要结束...')
    
    if __name__ == '__main__':
        print('父进程pid: %d' % os.getpid())  # os.getpid获取当前进程的进程号
        p = Process(target=run_proc)
        p.start()
    
    1. 每个进程都有1个数字来标记,这个数字称之为进程号
    2. Linux系统中查看PID的命令是 ps
    3. Linux命令 kill pid 的方式结束一个进程,进程结束,表示程序结束
  6. Process创建实例对象的常用方法
    1. start():启动进程实例(创建子进程)
    2. is_alive():判断子进程是否还在或者
    3. join([timeout]):是否等待子进程执行结束,或等待多时秒
    4. terminate():不管任务是否完成,立即终止子进程
  7. 创建进程方式2
    from multiprocessing import Process
    import time
     
    class MyNewProcess(Process):
        def run(self):
            while True:
                print('---1---')
                time.sleep(1)
     
    if __name__=='__mian__':
        p = MyNewProcess()
        # 调用p.start()方法,p会先去父类中寻找start(),然后在Process的start方法中调用run方法
        p.start()
     
        while True:
            print('---Main---')
            time.sleep(1)
    
  8. 传递参数
    from multiprocessing import Process
    import os
    from time import sleep
    
    
    def run_proc(name, age, **kwargs):
        for i in range(10):
            print('子进程运行中,name= %s,age=%d ,pid=%d...' % (name, age, os.getpid()))
            print(kwargs)
            sleep(0.2)
    
    if __name__=='__main__':
        p = Process(target=run_proc, args=('test',18), kwargs={"m":20})
        p.start()
        sleep(1)  # 1秒中之后,立即结束子进程
        p.terminate()
        p.join()
    
    1. 调用Process类创建进程对象时
      1. target指明 创建进程后,进程指定的代码是哪个函数
      2. args、kwargs用来给 那个函数指明传递的实参
        1. args:元组
        2. kwargs:字典
  9. 进程之间不共享全局变量

11.进程间通信-Queue

  1. 进程间相互独立,数据不共享,但有时需要数据共享,就需要进程间通信
  2. 可以使用multiprocessing模块的Queue实现多进程之间的数据传递
    from multiprocessing import Queue
    q = Queue(3)  # 初始化一个Queue对象,最多可接收三条put消息
    q.put("消息1")
    q.put("消息2")
    print(q.full())  # False
    q.put("消息3")
    print(q.full())  # True
    
    # 因为消息列队已满,所以会导致下面的try都会抛出异常,
    # 第一个try会等待2秒后再抛出异常
    # 第二个Try会立刻抛出异常
    try:
        q.put("消息4", True, 2)
    except:
        print("消息列队已满,现有消息数量:%s" % q.qsize())
    
    try:
        q.put_nowait("消息4")
    except:
        print("消息列队已满,现有消息数量:%s" % q.qsize())
    
    # 推荐的方式,先判断消息列队是否已满,再写入
    if not q.full():
        q.put_nowait("消息4")
    
    # 读取消息时,先判断消息列队是否为空,再读取
    if not q.empty():
        for i in range(q.qsize()):
            print(q.get_nowait())
    
    在这里插入图片描述
  3. 说明
    1. 使用Queue()时,若括号中没有指定最大可接收的消息数量,或数量为负值,那么就代表可接受的消息数量没有上限(直到内存的尽头)
    2. Queue的几个方法功能说明:
      1. Queue.qsize():返回当前队列包含的消息数量
      2. Queue.empty():如果队列为空,返回True,反之False
      3. Queue.full():如果队列满了,返回True,反之False
      4. Queue.get([block[, timeout]]):获取队列中的一条消息,然后将其从列队中移除,block默认值为True
        1. 如果block使用默认值,且没有设置timeout(单位秒),消息列队如果为空,此时程序将被阻塞(停在读取状态),直到从消息列队读到消息为止,如果设置了timeout,则会等待timeout秒,若还没读取到任何消息,则抛出Queue.Empty异常
        2. 如果block值为False,消息列队如果为空,则会立刻抛出Queue.Empty异常
      5. Queue.get_nowait():相当Queue.get(False)
      6. Queue.put(item,[block[, timeout]]):将item消息写入队列,block默认值为True
        1. 如果block使用默认值,且没有设置timeout(单位秒),消息列队如果已经没有空间可写入,此时程序将被阻塞(停在写入状态),直到从消息列队腾出空间为止,如果设置了timeout,则会等待timeout秒,若还没空间,则抛出Queue.Full异常
        2. 如果block值为False,消息列队如果没有空间可写入,则会立刻抛出Queue.Full异常
      7. Queue.put_nowait(item):相当Queue.put(item, False)

12.创建进程方式3-进程池

from multiprocessing import Pool
import os
import random
import time
 
def worker(num):
    for i in range(5):
        print('===pid=%d==num=%d='%(os.getpid(),num))
        time.sleep(1)
 
# 3表示进程池中最多有三个进程一起执行
pool=Pool(3)
 
for i in range(10):
    print('---%d---'%i)
    # 向进程中添加任务
    # 注意:如果添加的任务数量超过了进程池中进程的个数的话,那么就不会接着往进程池中添加,
    #	   如果还没有执行的话,他会等待前面的进程结束,然后在往
    # 进程池中添加新进程
    pool.apply_async(worker,(i,))
 
pool.close() # 关闭进程池
pool.join()  # 主进程在这里等待,只有子进程全部结束之后,在会开启主线程
  1. apply_async(func[, args[, kwds]]):使用非阻塞方式调用func(并行执行,堵塞方式必须等待上一个进程退出才能执行下一个进程),args为传递给func的参数列表,kwds为传递给func的关键字参数列表;
  2. close():关闭Pool,使其不再接受新的任务;
  3. terminate():不管任务是否完成,立即终止;
  4. join():主进程阻塞,等待子进程的退出, 必须在close或terminate之后使用;

13.进程池中的Queue

# -*- coding:utf-8 -*-

# 修改import中的Queue为Manager
from multiprocessing import Manager,Pool
import os,time,random

def reader(q):
    print("reader启动(%s),父进程为(%s)" % (os.getpid(), os.getppid()))
    for i in range(q.qsize()):
        print("reader从Queue获取到消息:%s" % q.get(True))

def writer(q):
    print("writer启动(%s),父进程为(%s)" % (os.getpid(), os.getppid()))
    for i in "itcast":
        q.put(i)

if __name__=="__main__":
    print("(%s) start" % os.getpid())
    q = Manager().Queue()  # 使用Manager中的Queue
    po = Pool()
    po.apply_async(writer, (q,))

    time.sleep(1)  # 先让上面的任务向Queue存入数据,然后再让下面的任务开始从中取数据

    po.apply_async(reader, (q,))
    po.close()
    po.join()
    print("(%s) End" % os.getpid())

运行结果:

(11095) start
writer启动(11097),父进程为(11095)
reader启动(11098),父进程为(11095)
reader从Queue获取到消息:i
reader从Queue获取到消息:t
reader从Queue获取到消息:c
reader从Queue获取到消息:a
reader从Queue获取到消息:s
reader从Queue获取到消息:t
(11095) End

14.案例:文件copy器(多进程版)

import multiprocessing
import os
import time
import random


def copy_file(queue, file_name,source_folder_name,  dest_folder_name):
    """copy文件到指定的路径"""
    f_read = open(source_folder_name + "/" + file_name, "rb")
    f_write = open(dest_folder_name + "/" + file_name, "wb")
    while True:
        time.sleep(random.random())
        content = f_read.read(1024)
        if content:
            f_write.write(content)
        else:
            break
    f_read.close()
    f_write.close()

    # 发送已经拷贝完毕的文件名字
    queue.put(file_name)


def main():
    # 获取要复制的文件夹
    source_folder_name = input("请输入要复制文件夹名字:")

    # 整理目标文件夹
    dest_folder_name = source_folder_name + "[副本]"

    # 创建目标文件夹
    try:
        os.mkdir(dest_folder_name)
    except:
        pass  # 如果文件夹已经存在,那么创建会失败

    # 获取这个文件夹中所有的普通文件名
    file_names = os.listdir(source_folder_name)

    # 创建Queue
    queue = multiprocessing.Manager().Queue()

    # 创建进程池
    pool = multiprocessing.Pool(3)

    for file_name in file_names:
        # 向进程池中添加任务
        pool.apply_async(copy_file, args=(queue, file_name, source_folder_name, dest_folder_name))

    # 主进程显示进度
    pool.close()

    all_file_num = len(file_names)
    while True:
        file_name = queue.get()
        if file_name in file_names:
            file_names.remove(file_name)

        copy_rate = (all_file_num-len(file_names))*100/all_file_num
        print("\r%.2f...(%s)" % (copy_rate, file_name) + " "*50, end="")
        if copy_rate >= 100:
            break
    print()


if __name__ == "__main__":
    main()

15.进程、线程对比

  1. 理解:
    1. 进程,能够完成多任务,比如一台电脑运行多个qq
    2. 线程,能够完成多任务,一个qq中的多个聊天窗口
  2. 定义
    1. 进程是系统进程资源分配和调度的一个独立单位
    2. 线程是进程的一个实体,是CUP调度和分派的基本单位,他是币进程跟小的独立运行的基本单位
  3. 区别
    1. 一个程序至少有一个进程,一个进程至少有一个线程
    2. 线程的划分尺度小于进程(资源比进程少),是的多线程程序的并发性高
    3. 进程在纸箱过程中拥有的独立内存单位,而多个线程共享内存,从而极大提高了程序的运行效率
    4. 线程不能够独立执行,必须依存在进程中
  4. 优缺点
    线程和进程在使用上各有优缺点
    1. 线程执行开销小,但不利于资源的管理和保护
    2. 进程执行开销大,但有利于资源的管理和保护
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值