python多任务

引入多任务

from time import sleep
def sing():
    for i in range(3):
        print("正在唱歌...%d"% i)
        sleep(1)
def dance():
    for i in range(3):
        print("正在跳舞...%d"% i)
        sleep(1)

if __name__=="__main__":
    sing()
    dance()#dance函数必须等待sing函数执行完毕后才能执行,所以一共要花费6秒钟
from time import sleep
import threading
def sing():
    for i in range(3):
        print("正在唱歌...%d"% i)
        sleep(1)
def dance():
    for i in range(3):
        print("正在跳舞...%d"% i)
        sleep(1)

if __name__=="__main__":
    t1=threading.Thread(target=sing)
    t2=threading.Thread(target=dance)
    t1.start()
    t2.start()

理解多任务

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

  • 并发:指的是任务数多余cpu核数,通过操作系统的各种任务调度算法,实现用多个任务“一起”执行(实际上总有一些任务不在执行,因为切换任务的速度相当快,看上去一起执行而已)
  • 并行:指的是任务数小于等于cpu核数,即任务真的是一起执行的
  • 真正的"并行"只能在多核CPU上实现,现实中由于任务数量远远多于CPU的核心数量,所以基本上都是“并发”。 操作系统会自动把很多任务轮流调度到每个核心上执行。

线程的介绍

先缕清一个概念:程序与进程
程序是存储在硬盘上的可执行的二进制文件
当操作系统将程序读入内存并为其分配资源开始运行后产生进程。
如果一个程序想同时执行多个部分的代码,可使用多进程,多线程实现
多进程一般是父进程fork出子进程(复制父进程的代码块等内容)去执行新的任务,并负责子进程的善后工作(wait),开销相对多线程大。
线程是一个抽象的概念,可以把它想象成程序在执行代码时的那个执行流,简单说一个进程中可以有多个线程,共享一些资源,开销小,所以也称线程是轻量级的进程

在Python中如果想使用线程实现多任务,可以使用thread模块 但是它比较底层,即意味着过程较为复杂不方便使用;推荐使用threading模块,它是对thread做了一些包装的,可以更加方便使用

通过第一节的示例可以看到多线程同时执行可以缩短执行时间当调用start函数时才会创建线程

多个线程执行时的顺序是不确定的

取决于CPU的调度顺序

from time import sleep,ctime
import threading

def task1():
    for i in range(10):
        print("task1.....%d"% i)
        sleep(0.1)

def task2():
    for i in range(10):
        print("task2.....%d"% i)
        sleep(0.1)

t1=threading.Thread(target=task1)
t2=threading.Thread(target=task2)

t1.start()
t2.start()
"""
task1.....0
task2.....0
task2.....1task1.....1#这里就是task2先执行

task2.....2task1.....2

task2.....3task1.....3

task2.....4task1.....4

task1.....5task2.....5

task1.....6
task2.....6
task1.....7
task2.....7
task2.....8task1.....8#又是task2先执行

task1.....9
task2.....9
"""

查看线程的数量

import threading
from time import sleep,ctime

def sing():
    for i in range(3):
        print("正在唱歌...%d" % i)
        sleep(0.1)

def dance():
    for i in range(3):
        print("正在跳舞...%d" % i)
        sleep(0.1)


print('---开始---:%s' % ctime())
t1 = threading.Thread(target=sing)
t2 = threading.Thread(target=dance)
t1.start()
t2.start()

while True:
    # threading.enumerate()能够得到当前这个程序中正在运行的所有任务,是一个列表
    length = len(threading.enumerate())
    print('当前运行的线程数为:%d' % length)
    if length <= 1:
        break
        # 延时一会,等等其他线程执行
        sleep(0.5)#放这个位置应该是执行不到应该缩进

创建线程时传递参数

传递位置参数

创建thread对象的时候target指定回调函数,args是一个元组,里面存放回调函数的参数,这里的参数是位置参数,跟形参一一对应

import threading
from time import sleep,ctime


def task1(num):
    for i in range(num):
        print("task1...%d"% i)
        sleep(0.1)

def task2(num1,num2):
    for i in range(num1):
        print("task2...i=%d,num2=%d"% (i,num2))#注意这里语法忘了
        sleep(0.1)


def main():
    t1=threading.Thread(target=task1,args=(3,))
    t2=threading.Thread(target=task2,args=(4,5))

    t1.start()
    t2.start()

    while True:
        length=len(threading.enumerate())
        print("thread  count %d"% length)
        if length <=1:
            break
        sleep(0.5)

传递关键字参数

import threading
from time import sleep,ctime


def work2(num1,num2,num3,n):
    print(f"---in work2--- {num1},{num2},{num3},{n}")

# t=threading.Thread(target=work2,args=(11,22,33),kwargs={"n":44})
t=threading.Thread(target=work2,args=(11,22),kwargs={"n":44,"num3":33})
t.start()

多线程UDP聊天程序

#注意这里的连接要处在同一个网段192.168.x    x要一致

import socket 

def send_msg(udp_socket):
    msg=input("\n请输入要发送的数据:")
    dest_ip=input("\n请输对方的IP:")
    dest_port=int(input("\n请输入对方的port:"))
    udp_socket.sendto(msg.encode('utf-8'),(dest_ip,dest_port))

def receive_msg(udp_socket):
    recv_msg=udp_socket.recvfrom(1024)
    recv_ip=recv_msg[1]
    recv_msg=recv_msg[0].decode('utf-8')
    print(">>>%s:%s" % (str(recv_ip),recv_msg))

def main():
    udp_socket=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
    udp_socket.bind(('',8080))

    while True:
        print("="*30)
        print("1,发送消息")
        print("2,接收消息")
        print("="*30)
        option=input("请选择功能:")
        if option=="1":
            send_msg(udp_socket)
        elif option=="2":
            receive_msg(udp_socket)
        else:
            print("输入有误,请重新输入!")

if __name__=="__main__":
    main()

#创建两个线程,一个用于收数据一个用于发数据
#由于这两个线程使用的是同一个终端,所以可能会出现正在数写发送数据时收到数据显示乱套
#这个程序中有三个线程,主线程属于空闲状态
import socket 
import threading
def send_msg(udp_socket):
    while True:
        dest_ip=input("\n请输对方的IP:")
        dest_port=int(input("\n请输入对方的port:"))
        while True:#防止每次都要询问IP PORT
            msg=input("\n请输入要发送的数据:")
            if msg:
                udp_socket.sendto(msg.encode('utf-8'),(dest_ip,dest_port))
            else:
                break

def receive_msg(udp_socket):
    while True:         
        recv_msg=udp_socket.recvfrom(1024)
        recv_ip=recv_msg[1]
        recv_msg=recv_msg[0].decode('utf-8')
        print(">>>%s:%s" % (str(recv_ip),recv_msg))

def main():

    udp_socket=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
    udp_socket.bind(('',8080))

    send_msg_t=threading.Thread(target=send_msg,args=(udp_socket,))
    recv_msg_t=threading.Thread(target=receive_msg,args=(udp_socket,))

    send_msg_t.start()
    recv_msg_t.start()


if __name__=="__main__":
    main()



创建线程的第二种方式

  1. 之前创建线程的方式
import threading

def task1(num1,num2):
	pass
	
t1=threading.Thread(target=task1,args=(num1,),kwargs={"num2":12})
t1.start()

  1. 使用类的方式创建线程
    1. 可以自己定义一个类,但这个类要继承Thread
    2. 一定要实现run方法,里面就是子线程要执行的代码
    3. 当调用start方法时,会创建线程,线程会自动调用run方法
    4. 如果类还定义了其他的方法,要想执行这些方法只能在run方法中调用
import threading
import time

class Task(threading.Thread):
    def run(self):
        while True:
            print("1111")
            time.sleep(1)

#创建一个对象
t=Task()
#这里由于Task类继承了threading.Thread类,父类中有start方法,此方法会自动调用子类的run方法
t.start()

#主线程打印信息,如果结果中有子线程打印的信息,说明子线程创建成功
while True:
    print("main.....")
    time.sleep(1)
 #python
 class Task(threading.Thread):
     def run(self):
         while True:
             print("1111")
             time.sleep(1)
             self.xxx()#只有在run方法中调用xxx方法xxx才会执行
 
     #xxx函数什么时候才能执行呢?
     def xxx(self):
         pass

并发TCP服务器

以前的代码只能针对一个客户端

import socket

#创建套接字
serv_s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#绑定本地信息
serv_s.bind(("",8080))
#监听
serv_s.listen(128)
#等待客户端连接
new_s,client_info=serv_s.accept()
print(client_info)

while True:
    #接收消息
    recv_content=new_s.recv(1024)
    
    if(len(recv_content)!=0):
        print(recv_content)
    else:
        #关闭客户端
        new_s.close()
        break
#关闭服务器套接字
serv_s.close
import socket
import threading

class HandleData(threading.Thread):
    def __init__(self,client_sock,client_info):
        super().__init__()#这里因为重写了父类的INIT方法所以要重新调用以下
        self.client_info = client_info#新套接字的一些IP PORT
        self.client_sock = client_sock#新创建的套接字

    def run(self):
        while True:
            recv_content=self.client_sock.recv(1024)
            if(len(recv_content)!=0):  
                #把信息回显给客户端
                massage= "%s port %d >>>"% (self.client_info[0],self.client_info[1])            
                self.client_sock.send(massage.encode("utf-8")+recv_content)
                #打印收到的信息,这里不用解码因为是二进制数据
                print(recv_content)
            else:
                self.client_sock.close()
                break


class TCPServer(threading.Thread):
    def __init__(self,port):
        super().__init__()

        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.sock.bind(("",port))
        self.sock.listen(128)

    def run(self):
        while True:
            #等客户端连接
            new_s,client_info=self.sock.accept()
            #打印客户段信息,这里是元组不是网络上传来的信息,直接解包就行,不用考虑解码的问题
            print("client %s contented on port %d" % (client_info[0],client_info[1]))

            #创建新的线程
            handle_data=HandleData(new_s,client_info)
            handle_data.start()
    
    def __del__():
        self.sock.close()
#这里创建主线程的时候指定了端口号
tcp_server = TCPServer(8080)
#主线程运行
tcp_server.start()



再次理解上节课代码

  1. 主线程__main__看到

    tcp_server = TCPServer(8080)

    tcp_server.start()时创建了一个线程1

  2. new_s,client_info=self.sock.accept()表示线程1一直在等待新的客户端连接,每来一个新客户则创建一个线程

    handle_data=HandleData(new_s,client_info)

    ​ handle_data.start()

    1. 子线程1创建的线程才是真正为客户端进行服务的

队列QUEUE

  1. 队列Queue
import queue

q=queue.Queue()
q.put('11')
q.put(22)
q.put({"name":100})

print(q.get())
print(q.get())
print(q.get())
  1. 堆栈Queue
import queue

q=queue.LifoQueue()
q.put('11')
q.put(22)
q.put({"name":100})

print(q.get())
print(q.get())
print(q.get())
  1. 优先级Queue
import queue
#前面的数字代表的是优先级,按优先级去值,数字越小优先级越高
q=queue.PriorityQueue()
q.put((10,"haha"))
q.put((30,"aha"))
q.put((20,"ha"))

print(q.get())
print(q.get())
print(q.get())

队列实现带有聊天记录的UDP聊天程序

import socket
import threading
import queue

class SendMsg(threading.Thread):
    def __init__(self,udp_sock,queue):
        super().__init__()
        self.udp_sock = udp_sock
        self.queue = queue

    def run(self):
        while True:
            print("1:发送数据")
            print("2:退出程序")
            op=input("请选择功能:")
            if op == "1":
                dest_ip=input("请输入对方的IP:")
                dest_port=int(input("请输入对方的port:"))
                while True:
                    msg=input("\n请输入要发送的数据:")
                    if msg:
                        self.udp_sock.sendto(msg.encode("utf-8"),(dest_ip,dest_port))
                        info="<<<(%s,%d):%s"%(dest_ip,dest_port,msg)
                        self.queue.put(info)
                    else :
                        break
            elif op == "2":
                break

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

class RecvMsg(threading.Thread):
    def __init__(self,udp_sock,queue):
        super().__init__()
        self.udp_sock = udp_sock
        self.queue = queue
    
    def run(self):
        while True:
            try:
                recv_msg=self.udp_sock.recvfrom(1024)
            except:
                break
            else:
                recv_ip=recv_msg[1]
                recv_msg=recv_msg[0].decode("gbk")
                info=">>>%s:%s"%(str(recv_ip),recv_msg)
                self.queue.put(info)

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

class SaveChat(threading.Thread):
    def __init__(self,queue):
        super().__init__()
        self.queue = queue

    def run(self):
        while True:
            with open("./chat.txt","a") as f:
                chat_info=self.queue.get()
                print("正在将%s写入到文件中。。。"% chat_info)
                f.write(chat_info)
                f.write("\n")


def main():
    #创建套接字
    udp_s=socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    #绑定
    udp_s.bind(("",8080))
    #创建一个队列
    q=queue.Queue()
    #创建三个线程
    udp_r=RecvMsg(udp_s,q)
    udp_s=SendMsg(udp_s,q)
    udp_chat=SaveChat(q)
    #线程运行
    udp_r.start()
    udp_s.start()
    udp_chat.start()


if __name__=="__main__":
    main()


互斥锁

线程间共享全局变量

import threading
import time
#全局变量可以使用g_开头 或者使用大写
NUM=0

def setNum():
    global NUM
    NUM=100
    print("Number of setNum=%d"%NUM)
#函数间用两个空行

def getNum():
    print("Number of getNum=%d"%NUM)

t1=threading.Thread(target=setNum)
t2=threading.Thread(target=getNum)

t1.start()
time.sleep(1)#这里主线程必须等待一段时间,因为两个线程的运行顺序是不确定的,可能t2先于t1执行
t2.start()

线程间共享全局变量是可能会产生问题

假如有三个线程:T1存完数据想让T3使用但由于操作系统调度算法的问题可能导致T2先于T3又修改了全局变量,这时T3得到的数据就不是目的数据了

全局变量可能会导致资源竞争

解释:

修改全局变量的操作被拆分成3步,task1第一步获取全局变量的值刚存储到他自己的内存空间中,接下来要执行加1的动作是被操作系统暂停,系统开始执行任务2,任务2执行完后全局变量变为1,接下来接着执行任务1,又在0 的基础上加一,全局变量变成1,跟我们预先想的2不一样,这就是资源竞争产生的问题,要解决这个问题只能在某一个线程操作全局变量时,其他线程只能等待其执行完再运行,原子操作。

互斥锁

加锁和解锁之间的代码不被打扰

下面的代码中三次打印#号不会被每秒打印helloworld打断

import threading
import time

def task1():
    while True:
        print("Hello world")
        time.sleep(1)


def task2(mutex):
    mutex.acquire()
    print("########")
    print("########")
    print("########")
    mutex.release()


mutex=threading.Lock()
t1=threading.Thread(target=task1)
t2=threading.Thread(target=task2,args=(mutex,))
t1.start()
t2.start()

在加锁和解锁中间再次调用加锁程序会卡住

互斥锁的使用

使用互斥锁之前,资源竞争导致最后得到的NUM不是2000000

import threading
import time

NUM=0

def task1(num):
    global NUM
    for i in range (num):
        NUM+=1
    print("task1 NUM=%d"% NUM)

def task2(num):
    global NUM
    for i in range (num):     
        NUM+=1
    print("task2 NUM=%d"% NUM)


mutex=threading.Lock()
t1=threading.Thread(target=task1,args=(1000000,))
t2=threading.Thread(target=task2,args=(1000000,))
t1.start()
t2.start()

使用互斥锁之后

task1 NUM=1909889
task2 NUM=2000000

解释一下出现这种情况的原因:task1加完1000000后打印出结果说明task1先执行完,task2才加到909889还没有执行完

import threading
import time

NUM=0

def task1(num):
    global NUM
    for i in range (num):
        mutex.acquire()
        NUM+=1
        mutex.release()
    print("task1 NUM=%d"% NUM)

def task2(num):
    global NUM
    for i in range (num):
        mutex.acquire()
        NUM+=1
        mutex.release()
    print("task2 NUM=%d"% NUM)


mutex=threading.Lock()
t1=threading.Thread(target=task1,args=(1000000,))
t2=threading.Thread(target=task2,args=(1000000,))
t1.start()
t2.start()

多进程

进程是运行起来的程序,以及其占用的资源

创建进程的方式

#from multiprocessing import Process
import time
import multiprocessing


def test():
    while True:
        print("-----test-----")
        time.sleep(1)


if __name__ == '__main__':
    p=multiprocessing.Process(target=test)
    p.start()#调用start方法时才会创建进程

    while True:
        print("-----main-----")
        time.sleep(1)

使用类的方式创建进程

import multiprocessing
import time

class MyNewProcess(multiprocessing.Process):
    def run(self):
        while True:
            print("-----1-----1-----1-----")
            time.sleep(1)

if __name__ == "__main__":
    p=MyNewProcess()
    p.start()
    while True:
        print("-----main-----")
        time.sleep(1)

给进程传递参数

import multiprocessing
import time

class MyNewProcess(multiprocessing.Process):
    def run(self):
        while True:
            print("-----1-----1-----1-----")
            time.sleep(1)

if __name__ == "__main__":
    p=MyNewProcess()
    p.start()
    while True:
        print("-----main-----")
        time.sleep(1)

进程不共享全局变量

import multiprocessing
import time

NUM=100

def task1():
    global NUM
    NUM=200
    print("task1 NUM is %d"%NUM)

def task2():
    print("task2 NUM is %d"%NUM)
p1=multiprocessing.Process(target=task1)
p2=multiprocessing.Process(target=task2)

p1.start()
time.sleep(1)#主进程睡眠1秒钟,等待task1修改全局变量
p2.start()

#task1 NUM is 200
#task2 NUM is 100
#两个进程打印的结果不一样,因为进程不共享全局变量
#进程的内存空间时独立的,子进程会复制父进程的一些东西

当一个程序运行时默认是主进程,进程中有一个线程叫主线程,主线程拿着代码和资源在运行,如果这时创建了另一个线程,这个线程会和主线程共享进程的资源

主进程运行时如果创建了另一个进程,新创建的进程会复制原进程的一些资源,但他们又各自的内存空间

进程间通信

QUEUE

import multiprocessing 

q=multiprocessing.Queue(3)
q.put("消息1")
q.put("消息2")
print(q.full())
q.put("消息3")
print(q.full())


try:
    q.put("消息4",timeout=2)#等待2秒如果消息还没放进去就产生异常
except:
    print("队列已满,现有消息的数量%d" % q.qsize())

try:
    q.put_nowait("消息4")#不等待直接方数据,如果放不进去直接抛异常
except:
    print("队列已满,现有消息的数量%d" % q.qsize())

if not q.full():
    q.put_nowait("消息4")

if not q.empty():
    for i in range(q.size()):
        print(q.get_nowait())

进程间 queue的使用

import multiprocessing
import time

def task1(q):
    for i in ['A', 'B', 'C']:
        q.put(i)


def task2(q):
    while True:
        time.sleep(1)
        if not q.empty():
            value=q.get()
            print("提取出来的数据是:",value)
        else:
            break


if __name__ == '__main__':
    q=multiprocessing.Queue()
    t1=multiprocessing.Process(target=task1,args=(q,))
    t2=multiprocessing.Process(target=task2,args=(q,))

    t1.start()
    t2.start()

子进程不是复制父进程的资源码,那么子进程中队列应该各是各的,为什么表现出来的是一个

进程和线程的比较

进程和线程都能实现多任务不同点是:

进程更类似于登录多个QQ号码,各是各的,每个号码对应一个进程

线程是QQ中可以同时跟多人进行窗口对话,每个窗口就代表一个线程

进程池

为什么需要进程池?

要拷贝1000000个文件,如果使用多进程创建同等数量的进程不一定是最优解,因为创建进程要消耗系统资源,释放进程也需要系统的开销,而且每一个进程在读取写入文件时也需要大量的时间,针对这种情况,我们可以创建一个进程池,里面又固定数量的进程以西拷贝文件就免去了创建大量进程的开销

进程池的使用

import multiprocessing
import time
import os
import random

def worker(num):
    for i in range(5):
        print("===pid===%d===num===%d="% (os.getpid(),num))
        time.sleep(1)

pool=multiprocessing.Pool(3)

for i in range(10):
    print("------%d-----"% i)
    """
    向进程池中添加任务
    如果任务数量超过进程的个数,不会再添加
    如果还没有执行
    """
    pool.apply_async(worker,(i,))#不会阻塞,后面的任务会放入到队列中等子进程执行完再执行

pool.close()#关闭进程池,表示不会在任务队列中添加任务
pool.join()#主进程等待所有子进程结束

你可能感兴趣的:(python,python,开发语言)