• 什么叫多任务呢?
  • 在现实生活中,有很多场景是同时进行的,比如唱歌跳舞,试想如果把这两者分开来,该有多滑稽。
  • 多任务其实就是操作系统同时处理多个任务,处理多任务得有多核处理器,所以在多核处理器普及之后,真正的多任务才实现。
  • 多任务可以分为并发并行,其中并发是“假多任务”,即单核处理器在极短的时间内循环处理多个任务,极短的时间给人造成一种“多任务”的错觉;而并行是“真多任务”,即每个核处理一个任务。
  • Python的多任务实现方式可分为“进程”,“线程”,“协程”,其中进程占用资源最大,但是稳定,线程占用资源和稳定性处于三者中间,协程是轻量级的,稳定性也较差一点。

线程

什么是“线程”呢?线程可以理解为程序中可以执行的一个分支。程序默认是有一个线程的,称为“主线程”,这个线程负责从上至下执行程序。在从上至下执行的过程中,有时候需要程序里面的多个函数一起执行,这时候就需要用到“多线程”。

如何创建多线程

Python创建多线程可以使用threading模块,下面看看我是怎么实现一下边唱歌边跳舞的:

import time
import threading

def sing():
    for i in range(5):
        print("sing")
        time.sleep(1)
        
def dance():
    for i in range(5):
        print("dance")
        time.sleep(1)
            
def main():
    t1 = threading.Thread(target=sing) # 创建子线程
    t2 = threading.Thread(target=dance)
    t1.start() # 子线程开始执行
    t2.start()
        
if __name__ == "__main__":
    main()
复制代码

注意:

  1. 创建子线程的的时候 target 传入的是目标函数的名称,是 不带括号 的;
  2. 线程运行起来是 无序; 的,是由操作系统决定的,主线程须等待子线程都执行完成以后才退出程序。

查看运行中的线程

threading里面有个enumerate()方法可以查看当前有哪些线程中运行,代码如下:

import threading
import time

def test1():
    for i in range(5):
        print("-----test1-----%d---" % i)
        time.sleep(1)

def test2():
    for i in range(5):
        print("-----test2-----%d---" % i)
        time.sleep(1)
        
def main():
    t1 = threading.Thread(target=test1)
    t2 = threading.Thread(target=test2)
    
    t1.start()
    time.sleep(1)
    
    t2.start()
    time.sleep(1)
    
    while True:
        print(threading.enumerate)
        time.sleep(1)
if __name__ == "__main__":
    main()
复制代码

多线程之间是共享全局变量的,但多线程中共享全局变量有个缺点是会出现竞争,如何解决资源竞争呢,这就涉及到“互斥锁”。

互斥锁

互斥锁的出现是为了解决几个线程之间产生的资源竞争,当多个线程需要同时进行一个全局变量进行处理的时候,互斥锁就派上用场了。这就跟上厕所关门一样,你关上门之后就不允许其他人进入这个厕所。 某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能修改,直到该线程释放资源,资源的状态变为“非锁定”,其他的线程才能再次锁定该资源。

import threading
import time

# 定义一个全局变量
g_num = 0


def test1(num):
    global g_num
    # 一般上锁会上在代码尽量少的地方,免得造成一个程序上锁之后长时间不解锁的情况
    for i in range(num):
        # 上锁,如果之前没有被上锁,那么此时上锁成功,
        # 如果之前已经被上锁,那么此时被堵塞,知道锁被解开
        mutex.acquire()
        g_num += 1
        # 解锁
        mutex.release()
    print("-------in test1 g_num=%d-----" % g_num)


def test2(num):
    global g_num
    for i in range(num):
        mutex.acquire()
        g_num += 1
        # 解锁
        mutex.release()
    print("-------in test2 g_num=%d-----" % g_num)


# 创建一个互斥锁,默认没有上锁
mutex = threading.Lock()


def main():
    t1 = threading.Thread(target=test1, args=(1000000,))
    t2 = threading.Thread(target=test2, args=(1000000,))

    t1.start()
    t2.start()

    time.sleep(5)

    print("-------in main Thread g_num = %d----" % g_num)


if __name__ == '__main__':
    main()
复制代码

结果

-------in main Thread g_num = 933690----
-------in test1 g_num=1999178-----
-------in test2 g_num=2000000-----
复制代码

为什么主线程会出现在子线程之前呢,这个是因为这5秒之内子线程没有完成计算造成的。

总结

线程是在“进程”,“线程”,“协程”之间处于“老二”的地位的,一般在我们写程序里面还是经常用到线程的,比如音乐播放器的边播放和边下载。