代码改变世界

python 多线程

2022-04-05 16:52  jym蒟蒻  阅读(80)  评论(0编辑  收藏  举报

python threading-单线程 多线程 主线程 子线程 setDeamon join

    • 单线程
    • 多线程
      • 主线程和子线程
      • setDaemon()
      • join()
      • 测试多线程下程序运行的时间
      • 创建多个线程
      • 优化子线程

 

单线程

用单线程 ,做听音乐和看电影两件事儿,首先排一下顺序。

import time

def music():
    for i in range(2):
        print("I was listening to music. %s" %time.ctime())
        time.sleep(1)

def move():
    for i in range(2):
        print("I was at the movies. %s" %time.ctime())
        time.sleep(5)

if __name__=='__main__':
    music()
    move()
    print("all over %s" %time.ctime())

输出结果:

I was listening to music. Wed Oct 20 15:42:19 2021
I was listening to music. Wed Oct 20 15:42:20 2021
I was at the movies. Wed Oct 20 15:42:21 2021
I was at the movies. Wed Oct 20 15:42:26 2021
all over Wed Oct 20 15:42:31 2021

time.sleep(1)是延时1s的意思。我们先听了一首音乐,通过for循环来控制音乐的播放了两次,每首音乐播放需要1秒钟,sleep()来控制音乐播放的时长。接着我们又看了一场电影,每一场电影需要5秒钟,通过for循环看两遍。在整个结束后,输出当前的时间。

可以发现:整个代码只能是先干一件事再干一件事。

用时:开始42分19结束是42分31,总耗时为12s。

多线程

cpu同时干多个活都没问题的,不需要让一件事一直占着cpu不放;于是,操作系统就进入了多任务时代。

python提供了threading模块来实现多线程。可以 引入threadring来同时播放音乐和视频。

import threading
import time

def music():
    for i in range(2):
        print("I was listening to music. %s" %time.ctime())
        time.sleep(1)

def move():
    for i in range(2):
        print("I was at the movies. %s" %time.ctime())
        time.sleep(5)
'''
创建了threads数组,创建线程t1,使用threading.Thread()方法,

在这个方法中调用music方法target=music,

把创建好的线程t1装到threads数组中。

接着以同样的方式创建线程t2,并把t2也装到threads数组。
'''
threads = []
t1 = threading.Thread(target=music)
threads.append(t1)
t2 = threading.Thread(target=move)
threads.append(t2)


if __name__=='__main__':
    for t in threads:
        t.setDaemon(True)
        t.start()

    print("all over %s" %time.ctime())

主线程和子线程

主线程:是执行主(main)方法的线程.

子线程:被Thread包含的“方法体”或者“委托”均为子线程

setDaemon()

当启动一个线程时设置thread.setDaemon(True),则该线程为守护线程(也可以称为后台线程)。表示该线程是不重要的,进程退出时不需要等待这个线程执行完成。这样做的意义在于:避免子线程无限死循环,导致退不出程序,也就是避免了孤儿进程的出现。

当不设置或者thread.setDaemon(False)时,主进程执行结束时,会等待线程结束。

上述程序如果设置t.setDaemon(False),那么将输出:

I was listening to music. Wed Oct 20 16:11:27 2021
I was at the movies. Wed Oct 20 16:11:27 2021
all over Wed Oct 20 16:11:27 2021
I was listening to music. Wed Oct 20 16:11:28 2021
I was at the movies. Wed Oct 20 16:11:32 2021

可以看到,在主线程进行完之后,也就是输出all over Wed Oct 20 16:11:27 2021之后,还会继续执行没完成的子线程。

如果设置setDaemon(True),那么将输出:

I was listening to music. Wed Oct 20 16:03:27 2021
I was at the movies. Wed Oct 20 16:03:27 2021
all over Wed Oct 20 16:03:27 2021

也就是说,子线程启动后,父线程也继续执行下去,当父线程执行完最后一条语句print(“all over %s” %time.ctime())后,没有等待子线程,直接就退出了,同时子线程也一同结束。

join()

import threading
import time

def music():
    for i in range(2):
        print("I was listening to music. %s" %time.ctime())
        time.sleep(1)

def move():
    for i in range(2):
        print("I was at the movies. %s" %time.ctime())
        time.sleep(5)

threads = []
t1 = threading.Thread(target=music)
threads.append(t1)
t2 = threading.Thread(target=move)
threads.append(t2)


if __name__=='__main__':
    for t in threads:
        t.setDaemon(True)
        t.start()

    for t in threads:
        t.join()

    print("all over %s" %time.ctime())

运行结果:

I was listening to music. Wed Oct 20 16:21:41 2021
I was at the movies. Wed Oct 20 16:21:41 2021
I was listening to music. Wed Oct 20 16:21:42 2021
I was at the movies. Wed Oct 20 16:21:46 2021
all over Wed Oct 20 16:21:51 2021

我们只对上面的程序加了个join()方法,用于等待线程终止。join()的作用是,在子线程完成运行之前,这个子线程的父线程将一直被阻塞。

也就是说,必须等待for循环里的两个进程都结束后,才去执行主进程。

从执行结果可看到,music 和move 是同时启动的。

开始时间21分41秒,结束时间为21分51秒,总耗时为10秒。比单线程用时减少了2秒。

测试多线程下程序运行的时间

现在把music的sleep()的时间调整为5秒。

import threading
import time

def music():
    for i in range(2):
        print("I was listening to music. %s" %time.ctime())
        time.sleep(5)

def move():
    for i in range(2):
        print("I was at the movies. %s" %time.ctime())
        time.sleep(5)

threads = []
t1 = threading.Thread(target=music)
threads.append(t1)
t2 = threading.Thread(target=move)
threads.append(t2)


if __name__=='__main__':
    for t in threads:
        t.setDaemon(True)
        t.start()

    for t in threads:
        t.join()

    print("all over %s" %time.ctime())
I was listening to music. Wed Oct 20 16:41:59 2021
I was at the movies. Wed Oct 20 16:41:59 2021
I was listening to music. Wed Oct 20 16:42:04 2021
I was at the movies. Wed Oct 20 16:42:04 2021
all over Wed Oct 20 16:42:09 2021

可以看到,最后还是用时10s。

如果是用单线程的话,用时将达到5*4=20s。

创建多个线程

从上面例子中发现线程的创建是颇为麻烦的,每创建一个线程都需要创建一个tx(t1、t2、…),如果创建的线程多时候这样极其不方便。下面对通过例子进行继续改进:

import threading
import time


def music(func):
    for i in range(2):
        print("Start music: %s! %s" % (func, time.ctime()))
        time.sleep(5)


def move(func):
    for i in range(2):
        print("Start playing: %s! %s" % (func, time.ctime()))
        time.sleep(5)
        
'''
创建了一个player()函数,这个函数用于判断播放文件的类型。如果是mp3格式的,我们将调用music()函数,如果是mp4格式的我们调用move()函数。哪果两种格式都不是那么只能告诉用户你所提供有文件我播放不了
'''

def player(name):
    r = name.split('.')[1]
    if r == 'mp3':
        music(name)
    else:
        if r == 'mp4':
            move(name)
        else:
            print("ERROR:The format is not recognized!")


'''
创建了一个list的文件列表,注意为文件加上后缀名。然后我们用len(list) 来计算list列表有多少个文件,这是为了帮助我们确定循环次数。
'''

list = ['jjj.mp3', 'yyy.mp4']

threads = []

files = range(len(list))

'''
通过一个for循环,把list中的文件添加到线程中数组threads[]中。
'''

for i in files:
    t = threading.Thread(target=player, args=(list[i],))
    threads.append(t)

'''
启动threads[]线程组,最后打印结束时间。
'''
if __name__ == '__main__':
    for i in files:
        threads[i].start()

    for t in files:
        threads[i].join()

    print("all over %s" %time.ctime())


运行结果:

Start music: jjj.mp3! Wed Oct 20 17:04:49 2021
Start playing: yyy.mp4! Wed Oct 20 17:04:49 2021
Start playing: yyy.mp4! Wed Oct 20 17:04:54 2021
Start music: jjj.mp3! Wed Oct 20 17:04:54 2021
all over Wed Oct 20 17:04:59 2021

其中,split()可以将一个字符串拆分成两部分,然后取其中的一部分。也就是说,判断list[i]里面后缀是mp3还是mp4类型的。

>>> x = 'testing.py'
>>> s = x.split('.')[1]
>>> if s=='py':
	print(s)

	
py

现在向list数组中添加一个文件,程序运行时会自动为其创建一个线程。

优化子线程

我们发现player()用于判断文件扩展名,然后调用music()和move() ,其实,music()和move()工作是类似的,可以改进一下,不管什么文件都可以播放。

import threading
import time

'''
创建player()函数,用于接收file和t,用于确定要播放的文件及时长。
'''
def player(file, t):
    for i in range(2):
        print("Start playing: %s! %s" % (file, time.ctime()))
        time.sleep(t)

'''
字典list ,用于定义要播放的文件及时长(秒),通过字典的items()方法来循环的取file和t,取到的这两个值用于创建线程。
'''

list = {'jjj.mp3': 3, 'yyy.mp4': 5, 'mmm.mp3': 4}

threads = []

files = range(len(list))

for file, t in list.items():
    tx = threading.Thread(target=player, args=(file, t))
    threads.append(tx)


if __name__ == '__main__':
    for i in files:
        threads[i].start()

    for t in files:
        threads[i].join()

    print("all over %s" %time.ctime())


运行结果:

Start playing: jjj.mp3! Wed Oct 20 17:50:43 2021
Start playing: yyy.mp4! Wed Oct 20 17:50:43 2021
Start playing: mmm.mp3! Wed Oct 20 17:50:43 2021
Start playing: jjj.mp3! Wed Oct 20 17:50:46 2021
Start playing: mmm.mp3! Wed Oct 20 17:50:47 2021
Start playing: yyy.mp4! Wed Oct 20 17:50:48 2021
all over Wed Oct 20 17:50:51 2021