线程

2020-08-15  本文已影响0人  小吉头

什么是线程

代码由静态变为动态运行的时候,负责执行代码的东西就叫线程

如何使用线程

一、通过threading.Thread类初始化


当主线程执行了t1.start(),此时创建子线程t1,需要执行的函数是sing。然后主线程和子线程在操作系统的时间片轮转下随机执行。当又轮到主线程时,t2.start()创建了子线程t2,此时主线程、子线程t1、子线程t2在时间片轮转下随机执行。
注意:
1、当主线程下面没有代码时,必须等待子线程结束,因为主线程一旦结束整个程序就结束了
2、当创建了线程对象t1、t2,此时没有创建子线程,只有调用start()才会有子线程,执行的代码就是target指向的函数,当子线程对应的target函数执行完成,子线程就结束了

join()的作用:

join()会等待子线程执行完成,再执行后面的语句

import threading
import time

def sing():
    time.sleep(1)
    print('sing')


def dance():
    time.sleep(2)
    print('dance')

if __name__ == "__main__":
    t1 = threading.Thread(target=sing)
    t2 = threading.Thread(target=dance)
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print("我是主线程")


>>>sing
>>>dance
>>>我是主线程

setDaemon()的作用

默认情况下,主线程执行完会等待子线程执行完再退出,子线程如果设置了setDaemon(True),主线程无需等待,直接退出,子线程也就跟着结束了

import threading
import time

def sing():
    time.sleep(1)
    print('sing')


def dance():
    time.sleep(2)
    print('dance')

if __name__ == "__main__":
    t1 = threading.Thread(target=sing)
    t2 = threading.Thread(target=dance)
    t1.setDaemon(True)
    t2.setDaemon(True)
    t1.start()
    t2.start()
    print("我是主线程")

>>>我是主线程

二、继承自threading.Thread类

import threading
import time

class Sing(threading.Thread):
    def run(self):
        while True:
            print("sing")
            time.sleep(1)


class Dance(threading.Thread):
    def run(self):
        while True:
            print("dance")
            time.sleep(1)

if __name__ == "__main__":
    t1 = Sing()
    t2 = Dance()
    t1.start()
    t2.start()

流程跟上面的一样,只是继承了threading.Thread类,需要重写run()方法,最终还是通过start()去创建子线程并执行run()方法

线程间通信

一、主线程和子线程共享全局变量

共享全局变量的目的是让多线程之间可以协作完成任务。假设音乐播放器开启一个子线程下载歌曲,歌曲内容放到全局变量中,主线程从全局变量读取内容播放,形成了边下载边放歌的多任务

注意:下图示例代码中,如果只是获取全局变量的值,是不需要加global的,如果要修改全局变量,得看全局变量是不是可变类型,如果是可变,可以不加global直接修改,如果不可变,必须要加global才能修改



python中哪些可变,哪些不可变,以及在内存中是如何存储的,后面的文章会介绍

多线程同时修改全局变量容易出问题:

如下代码,两个线程对全局变量同时修改:

import threading
import time

num = 0

def test():
    global num
    for i in range(100000):
        num += 1
    print('t1结束')

def test1():
    global num
    for i in range(100000):
        num += 1
    print('t2结束')

if __name__ == "__main__":
    t1 = threading.Thread(target=test)
    t2 = threading.Thread(target=test1)
    t1.start()
    t2.start()
    time.sleep(1)
    print(num)
>>>t1结束
>>>t2结束
>>>176111

理论结果应该是20000,实际只有17万多,为什么会出现这种情况?
查看字节码:

import dis

num = 0

def numAdd():
    global num
    num += 1

dis.dis(numAdd)

>>>             0 LOAD_GLOBAL              0 (num)
                3 LOAD_CONST               1 (1)
                6 INPLACE_ADD
                7 STORE_GLOBAL             0 (num)
               10 LOAD_CONST               0 (None)
               13 RETURN_VALUE


num +=1被翻译成了前4条指令,当num=0时,两个线程同时执行,t1如果还没有来得及保存,由于时间片轮转,就先去执行了t2,此时t2的数据保存成功,num=1,又轮到t1继续执行,此时t1开始保存之前的值,num还是等于1。因此会出现跟理论值不符的情况,这种情况下多线程是不安全的。

二、通过队列实现通信

队列是线程安全的,具体原理需要再查看下队列的底层实现

import threading
from queue import Queue

def download_data(q):
    data = [1,2,3,4]
    #向队列中写入数据
    for temp in data:
        q.put(temp)


def deal_data(q):
    #从队列中获取数据
    resList = list()
    while True:
        data = q.get()
        resList.append(data)
        if q.empty():
            break
    print(resList)

def main():
    #1、创建一个队列
    q = Queue()
    #2、创建多个进程,将队列的引用传递过去
    t1 = threading.Thread(target=download_data,args=(q,))
    t2 = threading.Thread(target=deal_data,args=(q,))
    t1.start()
    t2.start()

if __name__ == "__main__":
    main()
上一篇 下一篇

猜你喜欢

热点阅读