线程(Threads)

1、开启线程

一、什么是线程:
    1.进程是资源分配的最小单位,线程是CPU调度的最小单位。每一个进程中至少有一个线程。
    2.主进程中的线程称为主线程,其他开启的线程称为子线程
二、为什么用线程:
    进程有两个缺点:
        1.进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。
        2.进程在执行的过程中如果遇到阻塞,例如等待输入,整个过程就会挂起。即使进程中有些工作不依赖于输入的数据,也将无法执行。
# 而这就是为什么还要用线程
代码:
    from threading import Thread

    def task():
        print('子线程')

    if __name__ == '__main__':
        t = Thread(target=task)
        t.start()
        print('主线程')

2、GIL全局解释器锁

GIL全局解释器锁的特点:
	1.python在设计之初就考虑到要在主循环中,同时只有一个线程在执行。
	2.虽然python解释器可以“运行”多个线程,但在任意时刻只有一个线程在解释器中执行。
    3.由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。
常识:
    1.python代码在解释器中执行,绝大部分在用:cpython解释器,少部分用:pypy解释器
    2.GIL锁在解释器中存在,他只能在cpython解释器中,pypy解释器中不存在
    3.起一个垃圾回收线程,起一个正常执行的线程,垃圾回收还没有回收完毕,另一个线程可能会抢占资源
    4.设置一把全局解释器锁(GIL锁),有了这把锁,就保证同一时刻,只能有一个线程执行,只要线程想执行,那么,就必须拿到这把GIL锁
    5.如果是IO密集型:选择线程,因为io不会用到cpu内核,线程在进程里了
    6.如果是计算密集型:选择进程 # 计算密集型用多进程,计算io密集型用多线程

3、进程和线程的区别

一、开启效率的较量
    from multiprocessing import Process
    from threading import Thread
    import time
    # 开启进程的时间
    def task():
        time.sleep(1)
        print(123)

    if __name__ == '__main__':
        ctime = time.time()
        p = Process(target=task)
        p.start()
        p.join()
        print(time.time() - ctime)
        # 输出结果:1.0815215110778809

    # 开启线程的时间
    def task():
        time.sleep(1)
        print(123)

    if __name__ == '__main__':
        ctime = time.time()
        t = Thread(target=task)
        t.start()
        t.join()
        print(time.time() - ctime)
        # 输出结果:1.0080914497375488
# 结论:进程消耗比线程消耗大很多
二、多线程和多进程进行pid的比较
    # 进行pid的较量
    from multiprocessing import Process
    from threading import Thread
    import os
    def task():
        print('hello',os.getpid())

    if __name__ == '__main__':
        # 注:开启多个进程,每个进程都有不同的pid
        p1 = Process(target=task)
        p2 = Process(target=task)
        p1.start()
        p2.start()
        print('主进程/主进程pid',os.getpid())

        # 注:在主进程下开启多个线程,每个线程都跟主进程pid一样
        t1 = Thread(target=task)
        t2 = Thread(target=task)
        t1.start()
        t2.start()
        print('主进程/主进程pid',os.getpid())
# 结论:开启多进程,每个进程的pid都是不同的。在主程序下开启多线程,每个线程pid都和主程序pid一样。

4、Thread类的其他方法

一、Thread实例对象的方法:
    1.is_alive():返回线程是否活动
    2.getName();返回线程的名称
    3.setName():设置线程名称
二、threading模块提供的一些方法:
    1.threading.currentThread():返回当前的线程变量
    2.threading.enumerate():返回一个包含正在运行的线程的list。
    3.threading.activeCount():范慧慧正在运行的线程数量,与len(threading.enumerate())有相同的结果。
代码:
    from threading import Thread
    import time

    def task():
        time.sleep(1)
        print(123)

    if __name__ == '__main__':
        t = Thread(target=task)
        t.start()
        print(t.is_alive()) # 返回线程是否活动
        print(t.getName()) # 返回线程的名称
        t.setName('mmm') # 设置线程的名称
        print(t.getName())

5、守护线程

	p.isDaemon和t.setDaemon(True)的特点:主进程(线程)结束,子进程(线程)也随之结束。也根本不会运行子进程(线程)的程序。# 注意守护进程一定要放在start()之前,不然不会运行
代码:
    from threading import Thread
    import time	
    import os

    def task():
        time.sleep(1)
        print('my is %s'%(os.getpid()))

    if __name__ == '__main__':
        t =Thread(target=task)
        t.setDaemon(True)  # 跟进程不一样,进程的是p.isDaemon
        t.start()
        print('主线程')
        print(t.is_alive())
        # 返回结果:主线程,True,特点主线程结束,子线程也结束。

6、同步锁(互斥锁)

代码:
    from threading import Thread,Lock
    import time

    def task(lock):
        global n
        lock.acquire() # 上锁
        time.sleep(1)
        n -= 1
        lock.release() # 释放锁

    if __name__ == '__main__':
        n = 10
        l = []
        lock = Lock()
        for i in range(10):
            t = Thread(target=task,args=(lock, ))
            t.start()
            l.append(t)
        for j in l:
            j.join()
        print(n)	# 结果肯定为0,由原来的并发执行变成串行,牺牲了执行效率保证了数据安全
# 补充:Lock在Process中为进程锁,在Thread中为互斥锁。

7、信息量(Semaphore)

# Semaphore:信息量可以理解为多把锁,同时允许多个进程量更改数据
代码:
    from threading import Thread,Semaphore
    import time
    import random
    sm = Semaphore(2)  # 允许两个线程

    def task(i):
        sm.acquire()
        print('线程:%s进来了'%i)
        time.sleep(random.randint(1,3))
        print('线程:%s出去了'% i)
        sm.release()

    if __name__ == '__main__':
        for i in range(6):
            t = Thread(target=task,args=(i, ))
            t.start()

8、Enent事件

Enent事件的用法:
    1.一些线程需要等到其他线程执行完成之后才能执行,类似于发射信号
    2.比如一个线程等待另一个线程执行结束在继续执行
代码:
    from threading import Thread, Event
    import time
    
    def girl(event):
        print('女神正在谈恋爱中')
        time.sleep(3)
        event.set() # 发射信号
        print('女神分手了')
        
    def boy(i, event):
        print('屌丝:%s正在等待女神分手' % i)
        event.wait()  # 正在等待
        print('屌丝:%s开始展开追求' % i)

    if __name__ == '__main__':
        event = Event()
        t = Thread(target=girl, args=(event,))
        t.start()

        for i in range(5):
            t1 = Thread(target=boy, args=(i, event))
            t1.start()
posted @ 2021-09-02 19:20  迷恋~以成伤  阅读(298)  评论(0)    收藏  举报