一: threading模块的介绍

二: 开启线程的两种方式

三: 在一个进程下开启多个线程与在一个进程下开启多个子进程的区别

四:线程相关的其他方法

五:守护线程

六:python GIL(Glo罢了Interpreter Lock)全局解释器锁

七: 同步锁

八:死锁现象与递归锁

九:信号量Semaphore

十:Event事件

十一:条件condition(了解)

十二:定时器

十三:线程queue

十四:python标准模块---concurrent.futures

一:threading模块介绍

跟multiprocess类似  开多进程的

from threading import Thread

二:开启线程的两种方式

方法一:

from threading import  Thread
import time
def sayhi(name):
    time.sleep(2)
    print("%s say hello" %name)
if __name__ == '__main__':
    t=Thread(target=sayhi,args=("egon",))
    t.start()
    print("--master thread")

 方法二:

 

from threading import  Thread
import time
class Sayhi(Thread):
    def __init__(self,name):
        super().__init__()
        self.name=name
    def run(self):
        time.sleep(2)
        print("%s say hello" % self.name)
if __name__ == '__main__':
    t=Sayhi("egon")
    t.start()
    print("--master---")

 三: 在一个进程下开启多个线程与在一个进程下开启多个子进程的区别

from threading import Thread
from multiprocessing import Process
import os

def work():
    print('hello')

if __name__ == '__main__':
    #在主进程下开启线程
    t=Thread(target=work)
    t.start()
    print('主线程/主进程')
    '''
    打印结果:
    hello
    主线程/主进程
    '''

    #在主进程下开启子进程
    t=Process(target=work)
    t.start()
    print('主线程/主进程')
    '''
    打印结果:
    主线程/主进程
    hello
    '''
谁的开启速度快

 实践证明:开启子线程开销小,速度快。

四:练习

三个任务,一个接收用户输入,一个将用户输入的内容格式化程大写,一个将格式化后的结果存入文件

from threading import Thread
msg_l=[]
format_l=[]
def talk():
    while True:
        msg=input(">>: ").strip()
        if not msg:continue
        msg_l.append(msg)
def format_msg():
    while True:
        if msg_l:
            res=msg_l.pop()
            format_l.append(res.upper())
def save():
    while True:
        if format_l:
            with open("db.txt","a+",encoding="utf-8") as f:
                res=format_l.pop()
                f.write("%s\n" %res)
if __name__ == '__main__':
    t1=Thread(target=talk)
    t2=Thread(target=format_msg)
    t3=Thread(target=save)
    t_l=[t1,t2,t3]
    for t in t_l:
        t.start()

五: 线程相关的其他方法

Thread 实例对象的方法

#isAlive()返回线程是否是活动的

#getName()返回线程名

#setName()设置线程名

threading模块提供的一些方法:

#threading.currentThread()返回当前的线程变量

#threading.enumerate()返回一个包含正在运行的线程的list。正在运行 指线程启动后,结束前,不包括启动前和终止后的线程

#threading.activeCount():返回正在运行的线程数量,与len(threading.enumerate())有相同的结果

from threading import  Thread
import time
def sayhi(name):
    time.sleep(2)
    print("%s say hello"%name)
if __name__ == '__main__':
    t=Thread(target=sayhi,args=("hehe",))
    t.start()
    t.join() #主线程等待子线程结束
    print("master--")
    print(t.is_alive())

 六:守护线程

无论是进程还是线程,都遵循:守护进程(线程) 会等待主进程(线程)运行完毕后自动销毁。

需要强调的是:运行完毕并非终止运行

1 对主进程来说,运行完毕指的是主进程代码运行完毕

2 对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕

from threading import  Thread
import  time
def foo():
    print("123")
    time.sleep(4)
    print("end123")
def bar():
    print("456")
    time.sleep(3)
    print("end456")
if __name__ == '__main__':

    t1=Thread(target=foo)
    t2=Thread(target=bar)

    t1.daemon=True #t1 设为守护线程 主线程运行完后,t1就自动销毁,所以不会打印“end123”
            设置守护进程或者守护线程都必须在start之前设置才有效
t1.start() t2.start() print("master---")

 七:python GIL(global Interpreter lock)

pass

八;同步锁

三个需要注意的点:

1 线程抢的是GIL锁,GIL锁相当于执行权限,拿到执行权限后才能拿到互斥锁lock,其他线程也可以抢到GIL,但如果发现lock仍然没有别释放则阻塞,即便是拿到执行权限GIL也要立刻叫出来

2 join是等待所有,即整体串行,而锁只是锁住修改共享数据的部分,即部分串行,要想保证数据安全的根本原理在于让并发变成串行,join与互斥锁都可以实现,毫无疑问,互斥锁的部分串行效率更高

GIL vs lock

首先我们需要达成共识:锁的目的就是为了保护共享的数据,同一时间只能有一个线程来修改共享的数据

然后,我们可以得出结论:保护不同的数据就应该加不用的锁

GIL与Lock是两把锁,只是保护的数据不一样。

GIL是解释器级别的锁。它保护的当然是解释器级别的数据了,比如垃圾回收的数据

Lock是保护用户自己开发的应用程序的数据,只能用户自定义加锁处理

九:死锁现象与递归锁

进程,线程都有死锁和递归锁。

所谓死锁:是指两个或者两个以上的额进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若为外力作用,他们都无法推进下去。

此时程系统处于死锁状态或系统产生了死锁,这些永远互相等待的进程成为死锁进程。

from threading import Thread,Lock
import time
mutexA=Lock()
mutexB=Lock()

class MyThread(Thread):
    def run(self):
        self.func1()
        self.func2()
    def func1(self):
        mutexA.acquire()
        print('\033[41m%s 拿到A锁\033[0m' %self.name)

        mutexB.acquire()
        print('\033[42m%s 拿到B锁\033[0m' %self.name)
        mutexB.release()

        mutexA.release()

    def func2(self):
        mutexB.acquire()
        print('\033[43m%s 拿到B锁\033[0m' %self.name)
        time.sleep(2)

        mutexA.acquire()
        print('\033[44m%s 拿到A锁\033[0m' %self.name)
        mutexA.release()

        mutexB.release()

if __name__ == '__main__':
    for i in range(10):
        t=MyThread()
        t.start()

'''
Thread-1 拿到A锁
Thread-1 拿到B锁
Thread-1 拿到B锁
Thread-2 拿到A锁
然后就卡住,死锁了
'''
死锁

解决方法:递归锁

在python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock

这个RLock内部维护着一个lock和一个counter变量,counter 记录了acquire 的次数,从而使的资源可以被多次require直到一个线程所有的acquire都被release,其他的线程才能获得资源。啊哈那个面的离职如何使用Rlock代理lock,则不会发生死锁

mutexA=mutexB=threading.RLock() #一个线程拿到锁,counter加1,该线程内又碰到加锁的情况,则counter继续加1,这期间所有其他线程都只能等待,等待该线程释放所有锁,即counter递减到0为止
from threading import  Thread,Lock,current_thread,RLock
import  time
mutexA=mutexB=RLock() #mutexA=RLock();mutexB=RLock()就是这个意思
class Mythread(Thread):
    def run(self):
        self.f1()
        self.f2()
    def f1(self):
        mutexA.acquire()
        print("%s 拿到A所" %self.name)

        mutexB.acquire()
        print("%s 拿到B锁" %self.name)
        mutexB.release()
        mutexA.release()
    def f2(self):
        mutexB.acquire()
        print("%s 拿到B锁 "%self.name)
        time.sleep(0.1)
        mutexA.acquire()
        print("%s 按到A所" %self.name)
        mutexA.release()
        mutexB.release()
if __name__ == '__main__':
    for i in range(10):
        t=Mythread()
        t.start()

 十:信号量Semaphore

同进程的一样

Semphore管理一个内置的计数器

每当调用acquire()是内置计数器-1;

调用release()是内置计数器+1;

计数器不能小于0;当计数器为0时,acquire()将组设线程直到其他线程调用release()

from threading import  Thread,Semaphore
import threading
import time
def func():
    sm.acquire()
    print("%s get sm"%threading.current_thread().getName())
    time.sleep(3)
if __name__ == '__main__':
    sm=Semaphore(5) #这里的意思是我不管你产生了多少个线程,每次我只放5个进来
    for i in range(20):
        t=Thread(target=func)
        t.start()

 十一:Event

同进程一样

线程的一个关键特性是每个线程都是独立运行且运行状态不可预测。

如果程序中的其他线程需要公国判断牧歌线程的状态来确定自己下一步的操作,这是线程同步问题就会变得非常棘手

为了解决这些问题,我们需要使用threading卒中的event对象

对象包含一个可由线程设置的信号标志,它允许线程等待某些事情的发生。在初识情况下,event对象中的信号标志被设置为假。

如果有线程等待一个event对象,而这个event对象的标志为假,那么这个线程将会被已知阻塞直至该标志为真。

这个线程如果将一个event对象的信号标志设置为真,它将唤醒所有等待这个event对象的线程。如果一个线程等待一个已经被设置为真的event对象,那么它将忽略这个事件,继续执行

event.isSet():返回event的状态值

event.wait():如果event.isSet()将阻塞线程;

event.set():这是event的状态值为True,所有阻塞池的线程激活进入就绪状态,等待工作系统调度;

event.clear():恢复event的状态值为false

例如,有多个工作线程尝试连接mysql,我们想要在连接前确保mysql服务正常才让那些工作线程去连接mysql服务器,如果连接不成功,都回去尝试重新连接。

那么我们就可以采用threading.Event()机制来协调各个工作线程的连接操作

from threading import Thread,Event
import threading
import time,random
def conn_mysql():
    count=1
    while not event.is_set():
        if count > 3:
            raise TimeoutError('链接超时')
        print('<%s>第%s次尝试链接' % (threading.current_thread().getName(), count))
        event.wait(0.5)
        count+=1
    print('<%s>链接成功' %threading.current_thread().getName())


def check_mysql():
    print('\033[45m[%s]正在检查mysql\033[0m' % threading.current_thread().getName())
    time.sleep(random.randint(2,4))
    event.set()
if __name__ == '__main__':
    event=Event()
    conn1=Thread(target=conn_mysql)
    conn2=Thread(target=conn_mysql)
    check=Thread(target=check_mysql)

    conn1.start()
    conn2.start()
    check.start()

十二:条件conditon 了解

pass

十三:定时器

pass

十四线程queue

pass