Python全栈(四)高级编程技巧之7.Python多任务-线程的介绍和使用 - Go语言中文社区

Python全栈(四)高级编程技巧之7.Python多任务-线程的介绍和使用


一、多任务的介绍

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

import time


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


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


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

显示
result1
整个代码执行时间为6秒,两个函数执行是按照先后顺序执行的,不能实现同时执行,此时需要多任务,可以使多个任务同时进行。
对多任务的理解:
多任务
多任务是指多个任务同时进行。
在实际应用中,多个应用“同时”运行时,并不是真正地同时运行,而是按照时间片轮转的原理进行的。

  • 并发:
    假的多任务,CPU数小于当前执行的任务数;
  • 并行:
    真的多任务,CPU数大于等于当前执行的任务数。

线程举例说明:

import threading
import time


def demo():
    print('Hello World')
    time.sleep(1)


def main():
    for i in range(5):
        t = threading.Thread(target=demo)
        t.start()


if __name__ == '__main__':
    main()

显示
result2
其中,main()函数中为主线程,demo()函数为子线程;
target参数是需要执行的任务。
对前一段代码进行修改:

import time
import threading


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


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


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

显示
result3
显然,此时,sing()dance()两个函数是同时执行的,此即为多任务。
在代码中,t1和t2两个线程是同时执行、不分先后顺序的。
子线程结束之后主线程才会结束,下面进行验证:

import threading
import time


def demo():
    for i in range(5):
        print('Hello World')
        time.sleep(1)


def main():
    t = threading.Thread(target=demo)
    t.start()
    print('Main Thread')


if __name__ == '__main__':
    main()

显示:
result4
显然,主线程在等待子线程执行结束再结束。
要想主线程直接执行结束,可增加守护线程

import threading
import time


def demo():
    for i in range(5):
        print('Hello World')
        time.sleep(1)


def main():
    t = threading.Thread(target=demo)
    #守护线程
    t.setDaemon(True)
    t.start()
    print('Main Thread')


if __name__ == '__main__':
    main()

显示
result5
显然,只打印了一次Hello World,主线程并没有等子线程执行结束再结束,而是直接执行结束。
守护线程不会等子线程结束,而是直接结束。
进一步改进–主线程等待子线程结束之后再继续运行:

import threading
import time


def demo():
    for i in range(5):
        print('Hello World')
        time.sleep(1)


def main():
    t = threading.Thread(target=demo)
    t.start()
    # 等待子线程执行结束,主线程继续向下运行
    t.join()
    print('Main Thread')


if __name__ == '__main__':
    main()

显示:
result6
显然,主线程等待子线程打印完全部Hello World之后才打印Main Thread
并且,子线程还可以创建子线程

import threading
import time

def demo1():
    print('In demo1')


def demo():
    for i in range(5):
        print('Hello World')
        time.sleep(1)
        t2 = threading.Thread(target=demo1)
        t2.start()


def main():
    t = threading.Thread(target=demo)
    t.start()
    # 等待子线程执行结束,主线程继续向下运行
    t.join()
    print('Main Thread')


if __name__ == '__main__':
    main()

显示:
result7
易知,子线程的子线程也能正常运行,并且穿插在子线程的运行过程中运行。

二、查看线程数量

在基础Python中,enumerate()函数可以为列表自动创建索引:

l = ['a','b','c']
for i,item in enumerate(l):
    print(i,item)

打印

0 a
1 b
2 c

在多任务中,enumerate()函数可以查看当前线程的数量:

import threading


def demo1():
    for i in range(5):
        print('--demo1-- %d' % i)


def demo2():
    for i in range(5):
        print('--demo2-- %d' % i)


def main():
    t1 = threading.Thread(target=demo1)
    t2 = threading.Thread(target=demo2)
    t1.start()
    t2.start()
    #获取当前程序所有的线程
    print(threading.enumerate())


if __name__ == '__main__':
    main()

打印

--demo1-- 0
--demo1-- 1
--demo1-- 2
--demo1-- 3
--demo1-- 4
--demo2-- 0
--demo2-- 1
[<_MainThread(MainThread, started 23280)>, <Thread(Thread-2, started 27600)>]
--demo2-- 2
--demo2-- 3
--demo2-- 4

易知,有两个线程:主线程和线程2。
但是再运行的结果可能是

--demo1-- 0
--demo1-- 1
--demo1-- 2
--demo1-- 3
--demo1-- 4
--demo2-- 0
--demo2-- 1
--demo2-- 2
--demo2-- 3
--demo2-- 4
[<_MainThread(MainThread, started 10816)>]

此时,只有主线程一个线程。
解释
线程运行是没有先后顺序的,谁先抢占到内存资源就会先执行,所以线程数是变化的。
进一步改变代码:

import threading
import time


def demo1():
    for i in range(5):
        print('--demo1-- %d' % i)
        time.sleep(0.5)


def demo2():
    for i in range(10):
        print('--demo2-- %d' % i)
        time.sleep(0.5)


def main():
    t1 = threading.Thread(target=demo1)
    t2 = threading.Thread(target=demo2)
    t1.start()
    t2.start()
    # 获取当前程序所有的线程
    while True:
        print(threading.enumerate())
        if len(threading.enumerate()) <= 1:
            break
        time.sleep(1)


if __name__ == '__main__':
    main()

显示:
result8
易知,开始时3个线程,执行5次后demo1()线程执行结束,还有两个线程,10次后demo2()也结束,只有主线程1个线程,while循环结束,主线程也结束。

三、验证子线程的创建与执行

当调用Thread的时候,不会创建线程;
当调用Thread创建出来的实例对象的start()方法的时候,才会创建线程以及开始运行这个线程。

import threading


def demo():
    for i in range(5):
        print('demo')


def main():
    print(threading.enumerate())
    t1 = threading.Thread(target=demo)
    print(threading.enumerate())
    t1.start()
    print(threading.enumerate())


if __name__ == '__main__':
    main()

其中一次执行结果为:

[<_MainThread(MainThread, started 28344)>]
[<_MainThread(MainThread, started 28344)>]
demo
demo
demo
[<_MainThread(MainThread, started 28344)>, <Thread(Thread-1, started 30844)>]
demo
demo

显然,可得,在调用Thread()时不会创建子线程,只有在调用start()方法后才会创建线程并执行。

四、继承Thread类创建线程

继承类创建线程尝试:

import threading
import time


class A(object):
    def demo(self):
        for i in range(5):
            print('demo')
            time.sleep(0.5)


class B(object):
    def demo1(self):
        for i in range(5):
            print('demo1')
            time.sleep(0.5)


if __name__ == '__main__':
    t1 = threading.Thread(target=A().demo)
    t2 = threading.Thread(target=B().demo1)
    t1.start()
    t2.start()

打印

demo
demo1
demo1
demo
demo1
demo
demo1
demo
demo1
demo

虽然运用了类,但是与之前直接调用方法无本质区别,我们需要做的是继承自Thread类来创建线程。

import threading


class A(threading.Thread):
    # 该方法必须要重写且不能变方法名
    def run(self):
        for i in range(5):
            print(i)


if __name__ == '__main__':
    t1 = A()
    t1.start()

打印

0
1
2
3
4

类继承自Thread时,run()方法必须要重写,并且不能改名字。
验证:

import threading


class A(threading.Thread):
    def run1(self):
        for i in range(5):
            print(i)

    def demo(self):
        for i in range(5):
            print(i)


if __name__ == '__main__':
    t1 = A()
    t1.start()

运行改代码,无输出结果,不能实现预期的效果,因此必须要有run()方法。

五、线程间通信和传参

1.线程间通信

多线程共享全局变量又称为线程间通信。
问题:
在函数内部修改全局变量必须要声明global吗?

num = 100
li = [11,22]


def demo1():
    global num
    num += 100


def demo2():
    li.append(33)


def demo3():
    try:
        li += [44]
    except:
        print('error')


def demo4():
    global li
    li += [55]


print(num)
demo1()
print(num)

print(li)
demo2()
print(li)

print(li)
demo3()
print(li)

print(li)
demo4()
print(li)

打印

100
200
[11, 22]
[11, 22, 33]
[11, 22, 33]
error
[11, 22, 33]
[11, 22, 33]
[11, 22, 33, 55]

易知,数值型变量要想在函数内部改变,必须声明global;
列表在函数内部通过append()函数改变时,可以不声明global,但是要想通过直接相加的方式改变,必须要声明global。
解释:
列表通过直接相加的方式会改变原列表的指向,即原列表增加元素后会成为新的列表;
通过append()方法改变列表的元素后不改变原列表的指向,即还是原来的列表对象。
从而可得:
在函数中是否需要加global来改变全局变量,需要看该变量的指向是否改变,如果改变,则需要加global,反之,如果未改变指向、仅仅是修改了指向的空间中的数据,则不需要加global。
从而也就回答了问题:
在函数内部修改全局变量不一定要声明global,要看是否对全局变量的指向进行了修改。

import threading
import time


num = 100


def demo1():
    global num
    num += 1
    print('demo1---%d' % num)


def demo2():
    print('demo2---%d' % num)


def main():
    t1 = threading.Thread(target=demo1)
    t2 = threading.Thread(target=demo2)
    t1.start()
    time.sleep(1)
    t2.start()
    time.sleep(1)
    print('main---%d' % num)


if __name__ == '__main__':
    main()

显示:
result9
显然,三次打印都是101。
解释
在t1开始后,time.sleep(1)执行,所以不继续向下执行,而是执行demo1()中的代码,由于num声明了global,使num自增1,变为101,所以打印101;
1秒之后,执行demo2(),此时num已经变为101,所以会打印出101;
在过1秒后,main()函数中,打印出num为101。
可知,多线程是共享全局变量的,在一个线程改变的全局变量在其他线程中也能同步使用

2.多线程参数args

import threading
import time


num = [11,22]


def demo1(num):
    num.append(33)
    print('demo1---%s' % str(num))


def demo2(num):
    print('demo2---%s' % str(num))


def main():
    t1 = threading.Thread(target=demo1, args=(num,))
    t2 = threading.Thread(target=demo2, args=(num,))
    t1.start()
    time.sleep(1)
    t2.start()
    time.sleep(1)
    print('main---%s' % str(num))


if __name__ == '__main__':
    main()

打印

demo1---[11, 22, 33]
demo2---[11, 22, 33]
main---[11, 22, 33]
版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/CUFEECR/article/details/104167815
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢