多任务

  • 同一个时间有多个任务在执行
  • python程序默认是单任务

线程

  • 线程概念
    • 线程,可简单理解为是程序执行的一条分支,也是程序执行流的最小单元。
    • 线程是被系统独立调度和分底的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程为其它线程共享进程所拥有的全部资源。

image-20191113140837489

主线程

  • 当一个程序后动时,就有一个进程被操作系统(OS)创建,与此同时一个线程也立刻运行,该线程通常叫做程序的主线程,简而言之;程序后动就会创建一个主线程。

  • Copy主线程的重要性有两方面:

    1)是产生其他子线程的线程;

    2)通常它必须最后完成执行比如执行各种关闭动作·

子线程

  • 可以看做是程序执行的一条分支,当子线程后动后会和主线程一起同时执行
  • 主线程会等待所以子线程结束之后再结束
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import threading
from time import *


def loop0():
	print('start loop0 at:', ctime())
	sleep(4)
	print('loop0 done at:', ctime())


def loop1():
	print('start loop1 at:', ctime())	
	print('loop1 done at:', ctime())


def main():
	print('starting at :', ctime())
	# 使用threading.Thread创建对象(子进程对象)
	# threading.Thread(target=函数名)
	thread_1 = threading.Thread(target=loop0)
	thread_2 = threading.Thread(target=loop1)
	thread_1.start()
	thread_2.start()
	print('all done at:', ctime())


if __name__ == '__main__':
	main()
    
out
    starting at : Wed Nov 13 14:21:27 2019
    start loop0 at: Wed Nov 13 14:21:27 2019
    start loop1 at: Wed Nov 13 14:21:27 2019
    loop1 done at: Wed Nov 13 14:21:27 2019
    all done at: Wed Nov 13 14:21:27 2019
    loop0 done at: Wed Nov 13 14:21:31 2019

线程数量

  • 目标
    • 能够如何查看正在活动的线程数量
    • 1.查看线程数量
    • threading.enumerate()获取当前所有活跃的线程对象列表。使用len()对列表求长度可以看到当前活跃的线程的个数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import threading
from time import *


def loop0():
    print('{}start loop0 at{}:'.format(threading.current_thread(), ctime()))
    sleep(4)
    print('loop0 done at:', ctime())


def loop1():
    print('{}start loop1 at{}:'.format(threading.current_thread(), ctime()))
    print('loop1 done at:', ctime())


def main():
    print('starting at :', ctime())
    # 使用threading.Thread创建对象(子进程对象)
    # threading.Thread(target=函数名)
    thread_1 = threading.Thread(target=loop0)
    thread_2 = threading.Thread(target=loop1)
    thread_1.start()
    thread_2.start()
    print('all done at:', ctime())


if __name__ == '__main__':

    main()
    thread_list = threading.enumerate()
    print("当前线程数量:%d" % len(thread_list))

out:
    starting at : Wed Nov 13 14:41:20 2019
    <Thread(Thread-1, started 9916)>start loop0 atWed Nov 13 14:41:20 2019:
    <Thread(Thread-2, started 2868)>start loop1 atWed Nov 13 14:41:20 2019:
    loop1 done at: Wed Nov 13 14:41:20 2019
    all done at: Wed Nov 13 14:41:20 2019
    当前线程数量2
    loop0 done at: Wed Nov 13 14:41:24 2019

线程参数及顺序

  • 线程中传递参数有三种方法
    • 1.使用元组传递 threading.Thread(target=fun_name,args=(参数。。。)) thread_1 = threading.Thread(target=loop0, args=(10, 21, 22))
    • 2.使用字典传递 threading.Thread(target=fun_name,kwargs={"参数名": "参数值"....}) thread_1 = threading.Thread(target=loop0, kwargs={"a": 10, "b": 21, "c": 22})
    • 3.混合使用元组和字典传递 threading.Thread(target=fun_name,args=(10, 21, 22), kwargs={"参数名": "参数值"....}) thread_1 = threading.Thread(target=loop0, args=(10, 21), kwargs={"c": 22})
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import threading
from time import *


def loop0(a, b, c):
    print("参数:", a, b, c)
    print("start loop0 at:", ctime())
    sleep(4)
    print("loop0 done at:", ctime())


def loop1():
    print("start loop1 at:", ctime())
    print("loop1 done at:", ctime())


def main():
    print("starting at :", ctime())
    # 线程中传递参数有三种方法
    # 1.使用元组传递 threading.Thread(target=fun_name,args=(参数。。。))
    # thread_1 = threading.Thread(target=loop0, args=(10, 21, 22))

    # 2.使用字典传递 threading.Thread(target=fun_name,kwargs={"参数名": "参数值"....})
    # thread_1 = threading.Thread(target=loop0, kwargs={"a": 10, "b": 21, "c": 22})

    # 3.混合使用元组和字典传递 threading.Thread(target=fun_name,args=(10, 21, 22), kwargs={"参数名": "参数值"....})
    thread_1 = threading.Thread(target=loop0, args=(10, 21), kwargs={"c": 22})

    thread_2 = threading.Thread(target=loop1)
    thread_1.start()
    thread_2.start()
    print("all done at:", ctime())


if __name__ == "__main__":
    main()

out:
	starting at : Wed Nov 13 15:00:00 2019
    参数 10 21 22
    start loop0 at: Wed Nov 13 15:00:00 2019
    start loop1 at: Wed Nov 13 15:00:00 2019
    loop1 done at: Wed Nov 13 15:00:00 2019
    all done at: Wed Nov 13 15:00:00 2019
    loop0 done at: Wed Nov 13 15:00:04 2019

守护线程

  • 守护线程:如果在程序中将子线程设置为守护线程,则该子线程会在主线程结束时自动退出,设置方式为threaj.setDaemon(True),要在thread.start0之前设置,默认是false的,也就是主线程结束时,子线程依然在执行。
  • 对于python应用我们都知道main方法是入口,它的运行代表着主线程开始工作了,我们都知道Python虚拟机里面有垃圾回收器的存在使得我们放心让main运行,然而这背后是垃圾回收线程作为守护着主线程的守护线程。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import threading
import time


def work1():
    for i in range(10):
        print("正在执行work1...", i)
        time.sleep(0.5)


if __name__ == '__main__':
    # 创建子线程
    thread_woek1 = threading.Thread(target=work1)
    # 将子线程设置为守护线程
    thread_woek1.setDaemon(True)
    thread_woek1.start()

    # 睡眠
    time.sleep(2)
    print("game over")
    # 让程序退出,主线程主动结束
    exit()
    
out:
    正在执行work1... 0
    正在执行work1... 1
    正在执行work1... 2
    正在执行work1... 3
    game over

并行和并发

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

image-20191113153014444

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

image-20191113152928736

  • 并行:指的是任务数小于等于cpu核数,即任务真的是一起执行的
  • 并行:任务数量小于或等于CPU的核心数

d

多线程——共享全局变量

  • 当多个线程修改同一个资源的时候,会出现资源竞争,导致计算结果有误
  • 调用join方法优先让某个线程先执行
    • 缺点:将多线程变成了单线程,影响执行效率
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import threading
import time

g_num = 0

def work1():
    # 声明g_num是一个全局变量
    global g_num
    for i in range(10000000):
        g_num += 1
    print("work1-----------------", g_num)


def work2():
    global g_num
    for i in range(10000000):
        g_num += 1
    print("work2-----------------", g_num)


if __name__ == '__main__':

    work_1 = threading.Thread(target=work1)
    work_2 = threading.Thread(target=work2)

    work_1.start()
    # 优先让t1线程先执行, t1执行完毕后,t2才能执行
    work_1.join()
    work_2.start()

    while len(threading.enumerate()) != 1:
        time.sleep(1)

    print("main-----------", g_num)

同步和异步

  • 同步:多任务,多个任务之间执行的时候要求有先后顺序,必须一个先执行完成之后,另一个才能继续执行,只有一个主线。如:你说完,我再说(同一时间只能做一件事情)
  • 异步,指的是:多个任务之间执行没有先后顺序,可以同时运行,执行的先后顺序不会有什么影响,存在的多条运行主线。如:发微信(可以不用等对方回复,继续发)、点外卖(点了外卖后,可以继续忙其他的事情,而不是坐等外卖,啥也不做)

线程锁

image-20191113180221255

互斥锁

  • 当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制线程同步能够保证多个线程安全访问竞争源,最简单的同步机制是引入互斥锁。
  • 互斥锁为资源引入一个状态:锁定/非锁定
  • 某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。
  • 互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import threading
import time

g_num = 0


def work1():
    # 声明g_num是一个全局变量
    global g_num
    for i in range(10000000):
        # 上锁
        lock1.acquire()

        g_num += 1

        # 释放锁
        lock1.release()

    print("work1-----------------", g_num)


def work2():
    global g_num
    for i in range(10000000):
        # 上锁
        lock1.acquire()
        g_num += 1
        # 解锁
        lock1.release()

    print("work2-----------------", g_num)


if __name__ == '__main__':
    print(time.ctime())

    # 创建一把互斥锁
    lock1 = threading.Lock()

    work_1 = threading.Thread(target=work1)
    work_2 = threading.Thread(target=work2)

    work_1.start()
    work_2.start()

    while len(threading.enumerate()) != 1:
        time.sleep(1)

    print("main-----------", g_num)
    print("总时间:", time.ctime())

死锁

  • 在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁。
  • 注意:使用完毕及时释放

image-20191113203902756

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import threading


def get_value(index):

    data_list = [1, 3, 5, 7, 9]
    lock1.acquire()
    if index >= len(data_list):
        print("下标越界", index)
        # 若不释放就产生死锁
        lock1.release()
        return

    print(data_list[index])
    lock1.release()


if __name__ == '__main__':
    # 创建一把锁
    lock1 = threading.Lock()
    for i in range(10):
        t1 = threading.Thread(target=get_value, args=(i, ))
        t1.start()