不是并行,不是真正意义上的并发,可以单核实现并发。进程是资源单位(相当于车间),线程是运行单位(相当于生产线)

io多的项目,多线程更优于多进程

1 threading

  • 开启线程—函数
from threading import Thread
import time


def t_func(name, n):
    time.sleep(n)
    print("name:", name)


if __name__ == '__main__':
    t = Thread(target=t_func, args=("lynn", 4))
    t1 = Thread(target=t_func, args=("fancy", 1))
    t.start()
    t1.start()
    t.join() # 线程t完全运行完,才继续往下运行
    print("主")

注意:

target是函数名字,不加()

args是元组,必须按位置,只有一个参数时要加,

join方法,不加join方法,是异步的,加join是把异步变成同步,就是只有该线程完全运行完,才继续往下运行,不影响其他线程。

  • 开启线程—类
from threading import Thread
import time

class TClass(Thread):
    def __init__(self, name):
        super().__init__()
        self.name = name

    def run(self):
        time.sleep(1)
        print("name:", self.name)
        return self.name

if __name__ == '__main__':
    t = TClass("lynn")
    t1 = TClass("fancy")
    t.start()
    t1.start()
    t.join()
    print("主")

其他方法

getName()线程的名字

setName()设置线程的名字

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

  • 守护线程

主线程所在的进程内,所有的线程运行完毕,停止运行。其实也是线程运行完毕,停止运行

from threading import Thread
import time

class TClass(Thread):
    def __init__(self, n, name):
        super().__init__()
        self.name = name
        self.n = n

    def run(self):
        time.sleep(self.n)
        print("name:", self.name)
        return self.name


if __name__ == '__main__':
    t = TClass(1, name="lynn")
    t1 = TClass(5, name="fancy")
    t.daemon = True
    t.start()
    t1.start()
    print("主")

注意:

主线程所在的进程内所有的线程运行完毕,停止运行

daemon必须在start方法之前

2 线程数据安全和通信

线程锁

  • 互斥锁

用来实现对共享资源的同步访问,也称为同步锁

同一时间只有一个进程对加锁的数据进行操作。把该部分变成串行,切不运行完,不释放锁,会一直阻塞。

from threading import Thread
from threading import Lock
import time


class TClass(Thread):
    def __init__(self, n, name, lock):
        super().__init__()
        self.name = name
        self.n = n
        self.lock = lock

    def run(self):
        with self.lock:
            with open("t_text.txt", "wt", encoding="utf-8")as f:
                f.write(self.name)
            time.sleep(self.n)
            with open("t_text.txt", "rt", encoding="utf-8")as f:
                print("name:", f.read())
            print(self.name)


if __name__ == '__main__':
    lock = Lock()
    t = TClass(1, name="lynn", lock=lock)
    t1 = TClass(5, name="fancy", lock=lock)
    t.daemon = True
    t.start()
    t1.start()
    print("主")

注意:

GIL锁也是互斥锁,是解释器级别的互斥锁

尽量只在修改数据的部分加锁,因为会把并发转为串行,会影响效率

  • 死锁

两个或两个以上的线程(进程),在运行过程中两个线程(进程)互相等待,两个锁互相拿着没释放,没有外部原因,会一直阻塞,称为死锁现象。

死锁现象

import time
from threading import Thread
from threading import Lock

a_lock = Lock()
b_lock = Lock()


class DClass(Thread):
    def __init__(self, name):
        super().__init__()
        self.name = name

    def run(self):
        if self.name == "lynn":
            self.a_func()
        if self.name == "fancy":
            self.b_func()

    def a_func(self):
        a_lock.acquire()
        time.sleep(1)
        print("拿到a锁")
        b_lock.acquire()
        print("拿到b锁")
        a_lock.release()
        print("释放a锁")
        b_lock.release()
        print("释放b锁")

    def b_func(self):
        b_lock.acquire()
        time.sleep(1)
        print("拿到b锁")
        a_lock.acquire()
        print("拿到a锁")
        b_lock.release()
        print("释放b锁")
        a_lock.release()
        print("释放a锁")


dc1 = DClass("lynn")
dc2 = DClass("fancy")
dc1.start()
dc2.start()

注意:

线程A(进程) 拿着 锁a,要拿锁b释放锁a

进程B(进程) 拿着 锁b,要拿锁a释放锁b

锁a和锁b形成死锁现象

  • 递归锁RLock

解决死锁现象,针对多个锁的情况,递归锁可以被单个线程(进程)拿多次,每拿一次做一次标记,释放一次减去一个标记,标记为0时,才能被其他线程(进程)拿

import time
from threading import Thread
from threading import RLock

r_lock = RLock()

class DClass(Thread):
    def __init__(self, name):
        super().__init__()
        self.name = name

    def run(self):
        if self.name == "lynn":
            self.a_func()
        if self.name == "fancy":
            self.b_func()

    def a_func(self):
        r_lock.acquire()
        print("拿到a锁")
        r_lock.acquire()
        print("拿到b锁")
        r_lock.release()
        print("释放a锁")
        time.sleep(1)
        r_lock.release()
        print("释放b锁")

    def b_func(self):
        r_lock.acquire()
        time.sleep(1)
        print("拿到c锁")
        r_lock.acquire()
        print("拿到c锁")
        r_lock.release()
        print("释放c锁")
        r_lock.release()
        print("释放c锁")


dc1 = DClass("lynn")
dc2 = DClass("fancy")
dc1.start()
dc2.start()

注意:

只有被该线程(进程)全部释放才能被别的线程拿

线程间的通信

线程时相互独立的,数据是隔离的

  • Queue

管道:生产者消费者模型

from queue import Queue
from threading import Thread
import time


class SClass(Thread):
    def __init__(self, Q, name):
        super().__init__()
        self.Q = Q
        self.name = name

    def run(self):
        for i in range(100):
            self.Q.put("{}的{}包子".format(self.name, i))

class XClass(Thread):
    def __init__(self, Q):
        super().__init__()
        self.Q = Q
    def run(self):
        while True:
            time.sleep(0.1)
            res = self.Q.get()
            print(res)
            if not res:
                break

if __name__ == '__main__':
    Q = Queue(10)
    st = SClass(Q, "lynn")
    xc = XClass(Q)
    st.start()
    xc.start()
    st.join()
    Q.put(None)

3 ThreadPoolExecutor

支持线程池和进程池,python3.2之后版本

from concurrent.futures import ThreadPoolExecutor
import time


def thread_func(a):
    time.sleep(2)
    print("a")


if __name__ == '__main__':
    with ThreadPoolExecutor(max_workers=5)as t:
        res = t.submit(thread_func, 1)
        # print(res.result()) # 会阻塞
        r1 = t.submit(thread_func)
        print("end")
        # time.sleep(2)
        print(res.done())
        print(res.result())
    print(res.done())

注意:

submit方法,不阻塞,是异步的,第一个参数是方法名,后边按位置参数传方法需要的参数

with会等所有的线程运行完毕,才继续往下运行

done()查看线程的运行状态,True为运行完毕

result()线程的返回值,会阻塞

  • wait

开启多个线程

import time
from concurrent.futures import ThreadPoolExecutor, wait, ALL_COMPLETED


class TClass:
    @staticmethod
    def run():
        time.sleep(10)
        print("ok")


if __name__ == '__main__':
    with ThreadPoolExecutor(max_workers=5)as t:
        t_list = (t.submit(TClass.run), )
        wait(t_list, timeout=0.1, return_when=ALL_COMPLETED)
    print('end')

注意:

第一个参数必须是可迭代对象,最好是元组,里边的元素是submit()方法提交的数据

timeout超时时间,超过这个时间,该方法的阻塞时间,默认线程运行完才会继续往下运行

  • as_completed
import time
from concurrent.futures import ThreadPoolExecutor, as_completed


class TClass:
    @staticmethod
    def run(a, b):
        time.sleep(3)
        print("ok", a, b)
        return {"name": "lynn"}


if __name__ == '__main__':
    with ThreadPoolExecutor(max_workers=5)as t:
        t_list = [t.submit(TClass.run, 1, 2) for i in range(5)]
    res_list = as_completed(t_list) # 阻塞
    for i in res_list:
        print(i.result()) # 返回值

注意:

as_completed参数是可迭代对象:列表、元组等,元素是submit方法处理的线程

for循环能取到每个线程的结果

  • map
import time
from concurrent.futures import ThreadPoolExecutor


class TClass:
    @staticmethod
    def run(a, b):
        time.sleep(1)
        print('ok', a, b)
        return a, b


if __name__ == '__main__':
    with ThreadPoolExecutor(max_workers=5)as t:
        genera_res = t.map(TClass.run, (1,3), (2,3)) # 结果是生成器
        for i in genera_res:
            print(i) # 线程的返回值

注意:

map参数直接是方法,不用submit

参数以元组方式传递,多个参数多个元组,一个元组中多个参数表示调用多次