多任务

多任务

  • 多任务含义:

    • 生活:一边听歌,一边跳舞

    • 电脑:同时运行多个程序,如:qq,微信,陌陌,浏览器

  • 并发和并行

    • 并发:任务数大于核心数,通过操作系统调度算法实现多个任务“同时”执行,实际上通过快速切换任务,看上去是一起执行的,时间片轮转方式

    • 并行:任务数小于核心数,任务是真正一起执行

  • 进程:正在运行的一个程序我们可以说是一个进程 ,是系统进行资源分配和调用的独立单元,每

    一个进程都有自己独立的内存空间和系统资源

  • 程序:运行的应用程序称之为进程。当一个程序不运行的时候我们称之为程序,当程序运行起来就是一个进程。通俗的来说不运行的时候就是程序,运行起来就是进程。程序只有一个,但是进程可以有多个

  • 线程:线程是进程中的一条执行线路或者流程,程序执行的最小单位,线程是任务调度的最小单 位。

    • 由于进程是资源拥有者,创建、撤消与切换存在较大的内存开销,因此需要引入轻型进程 即线程,进程是资源分配的最小单位,线程是 CPU 调度的最小单位(程序真正执行的时候调 用的是线程)每一个进程中至少有一个线程
  • 进程和线程的关系:一个进程中可以有一到多个线程。一个线程只属于一个进程。一个进程中的多个线程是一种 竞争关系

创建线程

import time
import threading
#一边听歌,一边下载歌曲
def listen():
    for i in range(1,6):
        print("正在听歌")
        time.sleep(1)

def down_load():
    for i in range(1,6):
        print("正在下载歌曲")
        time.sleep(1)
# down_load()
# listen()

if __name__ == '__main__':
    #创建多线程
    t1 = threading.Thread(target=down_load)
    t2 = threading.Thread(target=listen)
    #开启线程
    t1.start()
    t2.start()
#执行顺序:程序运行时,代码从上向下走,走了if __name__ == '__main__':创建了两条线程,我们称之为子线程,程序运行时的线程我们称之为主线程
#子线程根据target=xxx,开始执行指定的函数
  • 线程注意点

    线程合适开启,何时结束
    1.子线程何时开启,何时运行
      当调用start()时,开启线程,再运行线程中的代码
    2.子线程何时结束
      子线程把target指向的函数中的语句执行完毕,当前子线程结束
    3.主线程何时结束
      所有子线程执行完毕后,主线程才结束
    4.查看线程数量:threading.enumerate(),可以列出当前运行的所有线程
    
  • 查看线程执行情况

    import threading
    import time
    
    def test1():
        for i in range(1,6):
            time.sleep(1)
            print("--子线程1--%d"%i)
            print("子线程1中查看线程情况",threading.enumerate())
    
    def test2():
        for i in range(1,6):
            time.sleep(1)
            print("--子线程2--%d"%i)
            print("子线程2中查看线程情况",threading.enumerate())
    
    def main():
        print("创建线程之前的线程情况",threading.enumerate())
    
        #创建线程
        t1 = threading.Thread(target=test1)
        t2 = threading.Thread(target=test2)
        time.sleep(0.1)
        print("创建线程之后的线程情况", threading.enumerate())
    
        #开启线程
        t1.start()
        t2.start()
        time.sleep(1)
        print("调用start之后的线程情况", threading.enumerate())
        time.sleep(6)
        print("等待子线程执行结束后的线程情况", threading.enumerate())
    if __name__ == '__main__':
        main()
    
  • args传递参数

    • 给函数传递参数,使用线程关键字args = ()进行传递参数,传递的参数是一个元组
    import time
    import threading
    #一边听歌,一边下载歌曲
    def listen(num):
        for i in range(1,num):
            print("正在听歌%d"%i)
            time.sleep(1)
    
    def down_load(num):
        for i in range(1,num):
            print("正在下载歌曲%d"%i)
            time.sleep(1)
    
    if __name__ == '__main__':
        #创建多线程
        t1 = threading.Thread(target=down_load,args=(10,))
        t2 = threading.Thread(target=listen,args=(10,))
        #开启线程
        t1.start()
        t2.start()
    
  • join方法

    • 功能:当前线程执行完后其他线程才会继续执行
    def main():
        #创建多线程
        t1 = threading.Thread(target=down_load,args=(5,))
        t2 = threading.Thread(target=listen,args=(5,))
        #开启线程
        t1.start()
        t1.join() #t1执行完之后,t2和主线程才会执行结束
        t2.start()
        # t2.join() #在t1,t2执行完后,再继续执行主线程
    
    if __name__ == '__main__':
        main()
        print("程序执行结束了")
    

setDaemon() 方法

  • setDaemon()将当前线程设置成守护线程来守护主线程:

-当主线程结束后,守护线程也就结束,不管是否执行完成 ,即主线程结束后不等待子线程,立即结束

-应用场景:qq 多个聊天窗口,就是守护线程

注意:需要在子线程开启的时候设置成守护线程,否则无效

import time
import threading
#一边听歌,一边下载歌曲
def listen(num):
    for i in range(1,num):
        print("正在听歌%d"%i)
        time.sleep(1)

def down_load(num):
    for i in range(1,num):
        print("正在下载歌曲%d"%i)
        time.sleep(0.5)
def main():
    #创建多线程
    t1 = threading.Thread(target=down_load,args=(5,))
    t2 = threading.Thread(target=listen,args=(5,))
    # t1.setDaemon(True)
    t2.setDaemon(True)
    #开启线程
    t1.start()
    t2.start()

if __name__ == '__main__':
    main()
    print("程序执行结束了")
  • threading模块提供的方法

    • threading。currentThread():返回当前的线程变量
    • threading.enumerate():可以列出当前正在运行的所有线程,正在运行的线程:启动后,结束前,不包括启动前和终止后
    • threading.activeCount()返回正在运行的线程数量,与len(threading.enumerate)有相同的结果
    • 线程.getName():获取线程名称
    • 线程.setName():设置线程名称
    • 线程.is_alive():判断线程存活状态
    def listen(num):
        for i in range(1,num):
            print("正在听歌%d"%i)
            time.sleep(1)
    
    def down_load(num):
        for i in range(1,num):
            print("正在下载歌曲%d"%i)
            time.sleep(0.5)
    def main():
        #创建多线程
        t1 = threading.Thread(target=down_load,args=(5,))
        t2 = threading.Thread(target=listen,args=(5,))
        print("t1线程开启之前的状态",t1.is_alive()) #False
        t1.setName("黄志")
        print("正在运行的线程数量:",threading.activeCount()) #1
        #开启线程
        t1.start()
        print("当前线程:",threading.currentThread()) #MainThread
        print(threading.enumerate())  #列表中2个线程
        print("正在运行的线程数量:", threading.activeCount()) #2
        t2.start()
        print("当前线程:", threading.currentThread()) #MainThread
        print(threading.enumerate()) #列表中3个线程
        print("正在运行的线程数量:", threading.activeCount()) #3
    
    if __name__ == '__main__':
        main()
        print("程序执行结束了")
        print("正在运行的线程数量:", threading.activeCount()) #3
    

使用继承方式开启线程

  • 定义一个类继承threading.Thread

  • 复写父类的run方法

    import threading
    import time
    class MyThread(threading.Thread):
        def run(self):
            for i in range(3):
                time.sleep(1)
                msg = f"I'm {self.name} @{i}"
                print(msg)
    def test1():
        for i in range(5):
            #创建线程
            t = MyThread()
            #开启线程
            t.start()
    if __name__ == '__main__':
        test1()
    

线程之间共享全局变量

def work1():
    g_num.append(44)
    print(f"--in work1,g_num is {g_num}") #[11,22,33,44]
def work2():
    print(f"--in work2,g_num is {g_num}") #[11,22,33,44]
t1 = threading.Thread(target=work1)
t2 = threading.Thread(target=work2)
g_num = [11,22,33]
t1.start()
time.sleep(1)
t2.start()
  • 总结:在一个进程内的所有线程共享全局变量,很方便在多个线程共享数据
  • 缺点:多线程对全局变量变量随意修改可能会造成数据混乱(线程非安全)

多线程开发可能会遇到的问题

import time
g_num = 0
def work1(num):
    global g_num
    for i in range(num):
        g_num+=1
    print(f"--in work1,g_num is {g_num}")
def work2(num):
    global g_num
    for i in range(num):
        g_num+=1
    print(f"--in work2,g_num is {g_num}")
t1 = threading.Thread(target=work1,args=(100,))
t2 = threading.Thread(target=work2,args=(100,))
t1.start()
t2.start()
#t1,t2两个线程都要对全局变量g_num进行加1运算,t1,t2各自对g_num加100次,那么最终结果应该是200

#但是由于是多线程同时操作,可能会出现以下问题
#在g_num=0,t1先取得g_num=0,此时系统将t1调为“sleeping”状态,把t2转换成“running”状态,t2也获取g_num=0,
#然后t2对获取到的值进行加1操作并赋值给g_num,使得g_num=1
#然后系统将将t2调为“sleeping”状态,把t1转换成“running”状态,线程t1把获取到的g_num=0加1后赋值给g_nun,使得g_num=1
#这样导致虽然t1和t2都对g_num加1,但结果是1

同步和异步

  • 同步:同步就是协同步调,按预定的先后次序进行运行,同是“协同”的意思
  • 异步:一个异步调用过程发出后,调用者在没得到结果之前可以进行后续操作。同步和异步是对应的,两个线程要么同步,要么异步

互斥锁

  • 当多个线程几乎同时修改一个共享数据的时候,需要进行同步控制,线程同步能够保证多个 线程安全的访问竞争资源(全局内容),最简单的同步机制就是使用互斥锁

  • 互斥锁是资源的一个引用状态:锁定/非锁定

  • 使用过程:某个线程要更改 共享数据时,先将其锁定,此时资源的状态为锁定状态,其他线程就不能更改,直到该线程将 资源状态改为非锁定状态,也就是释放资源,其他的线程才能再次锁定资源

    import time
    g_num = 0
    def work1(num):
        global g_num
        for i in range(num):
            mutex.acquire() #锁定
            g_num+=1
            mutex.release() #释放
    def work2(num):
        global g_num
        for i in range(num):
            mutex.acquire()  # 锁定
            g_num += 1
            mutex.release()  # 释放
    mutex = threading.Lock()
    t1 = threading.Thread(target=work1,args=(1000000,))
    t2 = threading.Thread(target=work2,args=(1000000,))
    t1.start()
    t2.start()
    while len(threading.enumerate())!=1:
        time.sleep(1)
    print(f"--in work2,g_num is {g_num}")
    
    
  • 锁的好处:

    • 确保了某段关键代码只能由一个线程从头到尾完整地执行
  • 锁的坏处:

    • 阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地 下降了
    • 由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁

死锁

  • 在多个线程共享资源的时候,如果两个线程同时占有一部分资源,并且同时等待对方的资源,就会造成死锁现象,尽管死锁很少发生,但是一旦发生就会造成应用程序的停止相应

    import time
    def test1():
        #lock1上锁
        lock1.acquire()
        print("--test1开始执行--")
        time.sleep(1)
        lock2.acquire()
        print("--test1执行结束--")
        lock2.release() #lock2解锁
        lock1.release()# lock1解锁
    
    def test2():
        #lock2上锁
        lock2.acquire()
        print("--test2开始执行--")
        # lock1上锁
        time.sleep(1)
        lock1.acquire()
        print("--test2执行结束--")
        lock1.release()
        lock2.release()
    
    lock1 = threading.Lock()
    lock2 = threading.Lock()
    if __name__ == '__main__':
        t1 = threading.Thread(target=test1)
        t2 = threading.Thread(target=test2)
        t1.start()
        t2.start()
    

生产者和消费者

  • 队列:Python 的 Queue 模块中提供了同步的、线程安全的队列类,包括 FIFO(先入先出)队列 Queue, LIFO(后入先出)队列 LifoQueue,和优先级队列 PriorityQueue。这些队列都实现了锁原语 (可以理解为原子操作,即要么不做,要么就做完),能够在多线程中直接使用。可以使用 队列来实现线程间的同步

    from queue import Queue
    q = Queue(maxsize=3)
    q.put("黄志")
    print(q.empty()) #False
    q.put("古印")
    q.put("王涛")
    print(q.full())
    # q.put("杨俊")
    print(q.get())
    print(q.get())
    print(q.get())
    print(q.get())
    # print(q.get(timeout=3))
    # print(q.get_nowait())
    print(q.qsize())
    
  • 生产者和消费者

    • 生产者:生成数据的线程
    • 消费者:处理数据的线程
    • 目的:平衡生产者和消费者的工作能力来提高程序的整理处理数据的速度
    import time
    import queue
    def producer(name):#生产者
        count = 1
        while True:
            print("%s生产了包子%d"%(name,count))
            q.put(count)
            count+=1
            time.sleep(1)
            print("包子总数:",q.qsize())
    def costom(name):#消费者
        while True:
            print("%s吃了包子%s"%(name,q.get()))
            time.sleep(0.5)
    if __name__ == '__main__':
        q = queue.Queue(maxsize=5)
        t1 = threading.Thread(target=producer,args=("黄志",))
        t2 = threading.Thread(target=costom,args=("吃货古",))
        t3 = threading.Thread(target=costom, args=("吃货刘",))
        t1.start()
        t2.start()
        t3.start()
    
  • 为什么使用生产者消费者模式

    • 在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当

      中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理

      完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必

      须等待生产者。为了解决这个问题于是引入了生产者和消费者模式

  • 什么是生产者消费者模式

    • 生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼 此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费
      者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队 列就相当于一个缓冲区,平衡了生产者和消费者的处理能力
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343