Python3学习(35)--多线程(一)

以后写博文,尽量减少文字,多一些demo实例演示,网上从不缺理论的东西,一搜一大片,这个说他讲的比较通俗易懂,那个又说,我的比你还要简洁还要有料,结果,点进去一看,要不就是简单的突出不了重点,要不就是懂了后感觉自己大脑空白,这个怎么结合实际用啊?????




一、单核CPU时代,多任务交替执行


案列:  一个人在家,边吃着炸鸡,边打着撸啊撸(当然,正常情况下,吃炸鸡不耽误玩游戏,但是,单核CPU不可能让你这两个动作同时进行,二者之间必须有个时间差,由于CPU执行时间太快,我们就不模拟真实情况了,我们用我们肉眼可以观察到的时间---秒级,来设置间隔时间)

目的:实现 吃炸鸡 和 撸啊撸 交替工作

效果:模拟CPU交替、顺序、反复地、执行任务,由于程序不可能一直运行,这里我们模拟一个可控状态,告诉CPU什么时候改终止动作!



single_work.py:

#!/usr/bin/env Python3
# -*- encoding:utf-8 -*-

from time import ctime,sleep

global done
done = True

def eat():
    print('我在吃炸鸡.....',ctime())
    sleep(1)


def play():
    print('我在撸啊撸.....',ctime())
    sleep(3)
    

if __name__ =='__main__': #作为本模块调试的时候用,非常方便!
    while(done):
        eat()
        play()
        n = input('是否要终止行为: No:1 or Yes:0\n')
        if n =='':
            done = True  
        else:
            done = int(n)
    print('吃完了,也撸完了,哈哈哈哈!')




主mian函数,一上来就来了个while循环,如果条件符合,吃和玩一直交替出现,如果输入流检测到了终止条件,立马结束吃和玩这两种动作!


效果演示:


敲回车,默认行为继续执行(交替交替),注意,执行时间是有间隔的(一会说多线程的时候,你会发现,吃 和 玩  可以到达'惊人'的同步,吓人不!!! 操作流弊啊!)


Python3学习(35)--多线程(一)_第1张图片


输入0 ,终止行为


Python3学习(35)--多线程(一)_第2张图片


弊端:    对方塔还剩最后一滴血,结果,这个时候你吃了口炸鸡(假设这种绝杀的机会交给你了),恰恰在这1s钟的时刻,对方英雄全部复活,杀到了你面前,结果,你挂了,后面就不说了,对面逆风翻盘全因你推塔的时候吃了口炸鸡啊,你说气人不气人! 你还委屈的说:"卧槽,什么情况?什么情况?什么情况!!!"


后来,你闭关修炼,发誓要吃的同时也可以推塔,绝不给对手留任何机会!!!



二、多核CPU时代,多任务并行执行


直接上demo,对上面的例子进行改进,引入,多线程编程模块 threading


multi_work.py:


#!/usr/bin/env Python3
# -*- encoding:utf-8 -*-

import threading
import time
from time import sleep


def eat(ntime):
    print('我在吃炸鸡.....',ntime)
    sleep(1)
    print('我吃完了!--->',ToStandardTime(time.time()))


def play(ntime):
    print('我在撸啊撸.....',ntime)
    sleep(3)
    print('我撸完了!--->',ToStandardTime(time.time()))
def ToStandardTime(Now):  #注意 Now表示 当前的 时间戳 --->可以转化为 年月日 时分秒毫秒
    
    #转换为其他日期格式,如:"%Y-%m-%d %H:%M:%S"
    local_time =time.localtime(Now)
    s_time = time.strftime("%Y-%m-%d %H:%M:%S", local_time)    
    #获得毫秒数
    ms     = int((Now - int(Now))*1000)   #其实就是时间戳的 小数部分
    return '%s.%d' %(s_time,ms)
    
'''
target: is the callable object to be invoked by the run()
        method. Defaults to None, meaning nothing is called.
  翻译: 指向一个被调用的对象,作为被唤起的 run()方法,默认None,意味着什么也没有被调用(run)
args  : is the argument tuple for the target invocation. Defaults to ().
  翻译:  就是target指向的  对象的 参数,当然,这是一个tuple  元组类型,默认空()
'''
t1 = threading.Thread(target=eat,args=(ToStandardTime(time.time()),))
t2 = threading.Thread(target=play,args=(ToStandardTime(time.time()),))

t1.start()  #启动线程1 --> 唤起  吃炸鸡 这个任务
t2.start()  #启动线程2 --> 唤起  撸啊撸 这个任务

print('主线程结束!,尔等子线程快快汇报情况!')

注释够详细吧!没有看不懂的地方吧?


我们看下效果(猜测一下,demo中总过有五处print打印函数,他们的执行顺序 是不是 你所看到的那样的呢?)


Python3学习(35)--多线程(一)_第3张图片


主线程,你不够意思吧,你还没等我子线程的任务完全结束(函数中短暂暂停后,还有一个任务就是 打印完成情况),你就先我俩一步,你让我们两个子线程情何以堪啊!



A、改进上述demo,使得,主线程一结束,两个子线程也跟着结束

(这就相当于,古代打仗的时候,将军一死,仗已输,士兵纷纷缴械投降)


#!/usr/bin/env Python3
# -*- encoding:utf-8 -*-

import threading
import time
from time import sleep


def eat(ntime):
    print('我在吃炸鸡.....',ntime)
    sleep(1)
    print('我吃完了!--->',ToStandardTime(time.time()))


def play(ntime):
    print('我在撸啊撸.....',ntime)
    sleep(3)
    print('我撸完了!--->',ToStandardTime(time.time()))
def ToStandardTime(Now):  #注意 Now表示 当前的 时间戳 --->可以转化为 年月日 时分秒毫秒
    
    #转换为其他日期格式,如:"%Y-%m-%d %H:%M:%S"
    local_time =time.localtime(Now)
    s_time = time.strftime("%Y-%m-%d %H:%M:%S", local_time)    
    #获得毫秒数
    ms     = int((Now - int(Now))*1000)   #其实就是时间戳的 小数部分
    return '%s.%d' %(s_time,ms)
    
'''
target: is the callable object to be invoked by the run()
        method. Defaults to None, meaning nothing is called.
  翻译: 指向一个被调用的对象,作为被唤起的 run()方法,默认None,意味着什么也没有被调用(run)
args  : is the argument tuple for the target invocation. Defaults to ().
  翻译:  就是target指向的  对象的 参数,当然,这是一个tuple  元组类型,默认空()
'''
t1 = threading.Thread(target=eat,args=(ToStandardTime(time.time()),))
t2 = threading.Thread(target=play,args=(ToStandardTime(time.time()),))

#daemon
# A boolean value indicating whether this thread is a daemon thread.
# 一个布尔值,标明此线程是否是守护线程
t1.setDaemon(True)  #守护线程,默认值是False,如需设置,请在start()函数被调用之前设置
t1.start()  #启动线程1 --> 唤起  吃炸鸡 这个任务

t2.setDaemon(True)
t2.start()  #启动线程2 --> 唤起  撸啊撸 这个任务


print('主线程结束!尔等子线程没必要再进行汇报了!')

光说什么是守护线程,估计也弄不明白,那我们就直接上执行后的结果,一看效果,你就明白过来了


Python3学习(35)--多线程(一)_第4张图片



如果你不设置守护线程的话或手动显示的设置False的话,效果和最开始的那个一样,放张图吧,对比下设置和不设置的区别(默认就是False)


Python3学习(35)--多线程(一)_第5张图片


B、改进上述demo,使得,两个子线程完全结束任务后,主线程才结束(符合常理)


#!/usr/bin/env Python3
# -*- encoding:utf-8 -*-

import threading
import time
from time import sleep


def eat(ntime):
    print('我在吃炸鸡.....',ntime)
    sleep(1)
    print('我吃完了!--->',ToStandardTime(time.time()))


def play(ntime):
    print('我在撸啊撸.....',ntime)
    sleep(3)
    print('我撸完了!--->',ToStandardTime(time.time()))
def ToStandardTime(Now):  #注意 Now表示 当前的 时间戳 --->可以转化为 年月日 时分秒毫秒
    
    #转换为其他日期格式,如:"%Y-%m-%d %H:%M:%S"
    local_time =time.localtime(Now)
    s_time = time.strftime("%Y-%m-%d %H:%M:%S", local_time)    
    #获得毫秒数
    ms     = int((Now - int(Now))*1000)   #其实就是时间戳的 小数部分
    return '%s.%d' %(s_time,ms)
    
'''
target: is the callable object to be invoked by the run()
        method. Defaults to None, meaning nothing is called.
  翻译: 指向一个被调用的对象,作为被唤起的 run()方法,默认None,意味着什么也没有被调用(run)
args  : is the argument tuple for the target invocation. Defaults to ().
  翻译:  就是target指向的  对象的 参数,当然,这是一个tuple  元组类型,默认空()
'''
t1 = threading.Thread(target=eat,args=(ToStandardTime(time.time()),))
t2 = threading.Thread(target=play,args=(ToStandardTime(time.time()),))

#daemon
# A boolean value indicating whether this thread is a daemon thread.
# 一个布尔值,标明此线程是否是守护线程
#t1.setDaemon(False)  #守护线程,默认值是False,如需设置,请在start()函数被调用之前设置
t1.start()  #启动线程1 --> 唤起  吃炸鸡 这个任务

#t2.setDaemon(False)
t2.start()  #启动线程2 --> 唤起  撸啊撸 这个任务
print('------------------')
#join: Wait until the thread terminates.
#翻译: 等待线程终止.
t1.join()  #---->这行demo的意思是:  主线程,你别急,我吃完炸鸡了  ,我给你汇报,你再判断是否结束!
t2.join()  #---->这行demo的意思是:  主线程,你别急,我撸啊撸打完了,我给你汇报,你再判断是否结束!
print('子线程结束(先)---->主线程结束(后)!')


上述demo,其实没有什么复杂的地方,我写demo有一个习惯,就是翻看Python环境自带的help函数查某个模块的功能函数的说明,然后把英文说明作为demo的注释部分,看得懂的话,就自己理解,看不懂的话就借助谷歌翻译,所以,有时候,我写注释可能要比实际写的demo都要多,哈哈,没办法,记性不好。


不多说了,我们看下执行效果


Python3学习(35)--多线程(一)_第6张图片




三、定时器,另一种多线程的实现(衍生)


#!/usr/bin/env Python3
# -*- encoding:utf-8 -*-

import threading
import time
from time import sleep


def eat(ntime):
    print('我在吃炸鸡.....',ntime)
    global timer1
    timer1 = threading.Timer(3,eat,args=(ToStandardTime(time.time()),))
    timer1.start()
    print('我吃完了!---->',ToStandardTime(time.time()))
    print('---------------------------------------------')


def play(ntime):
    print('我在撸啊撸.....',ntime)
    global timer2
    timer2 = threading.Timer(3,play,args=(ToStandardTime(time.time()),))
    timer2.start()
    print('我撸完了!---->',ToStandardTime(time.time()))
    print('---------------------------------------------')

def ToStandardTime(Now):  #注意 Now表示 当前的 时间戳 --->可以转化为 年月日 时分秒毫秒
    
    #转换为其他日期格式,如:"%Y-%m-%d %H:%M:%S"
    local_time =time.localtime(Now)
    s_time = time.strftime("%Y-%m-%d %H:%M:%S", local_time)    
    #获得毫秒数
    ms     = int((Now - int(Now))*1000)   #其实就是时间戳的 小数部分
    return '%s.%d' %(s_time,ms)


#Timer():Call a function after a specified number of seconds:
#   翻译:在指定的秒数之后调用一个函数,注意是调用(一次)!不是走秒、时钟的那种
#   因此,真正的定时部分还是要放到函数里面去实现!!!!
timer1 = threading.Timer(1,eat,args=(ToStandardTime(time.time()),))
timer1.start()

timer2 = threading.Timer(1,play,args=(ToStandardTime(time.time()),))
timer2.start()


注意,理解Timer函数的用法,它每次只调用一次,要想让一个被call的函数间隔一段时间就执行一次,需要在被call的函数内部继续用最开始的timer实例反复去创建自己的实例(记得在函数内部声明全局timer,否则待会取消的时候不管用),类似于递归调用,这样才能实现定时器的效果。


上述demo执行效果:


Python3学习(35)--多线程(一)_第7张图片


由于,定时器,不可能无限时的执行下去,因此,我们可以指定一个时间,时间一到,立马结束掉指定的定时器timer


我们修改上述demo如下(.....主demo就不贴出来了,和上面的一样,在最后贴出来如何取消定时器的demo)

time.sleep(10)
#timer1.cancel()
timer2.cancel()


主线程10s一到,就结束定时器timer2的任务,因此,10s以后,我们的行为只剩下了 吃炸鸡


Python3学习(35)--多线程(一)_第8张图片


你会发现一个有意思的地方,就是这里


Python3学习(35)--多线程(一)_第9张图片


两个任务,你设置的都是3秒以后执行,表面上都是同时执行,但是,由于定时器也是CPU任务调度的一种,任务虽然是同一时刻触发的,但是任务内容的打印却是有先后时间差的,因此,出现上述0.001毫秒的误差是在情理之中的~!


你可能感兴趣的:(Pyhon3.X学习)