多任务编程
线程(Thread)
线程概述
什么是线程
- 线程被称为轻量级的进程,也是多任务编程方式
 - 也可以利用计算机的多cpu资源
 - 线程可以理解为进程中再开辟的分支任务
 
线程特征
- 一个进程中可以包含多个线程
 - 线程也是一个运行行为,消耗计算机资源
 - 一个进程中的所有线程共享这个进程的资源
 - 多个线程之间的运行同样互不影响各自运行
 - 线程的创建和销毁消耗资源远小于进程
 

多线程编程

from threading import Thread 
t = Thread()
功能:创建线程对象
参数:target 绑定线程函数
     args   元组 给线程函数位置传参
     kwargs 字典 给线程函数键值传参
     daemon bool值,主线程推出时该分支线程也推出
#启动线程
t.start()
#等待分支线程结束
t.join([timeout])
功能:阻塞等待分支线程退出
参数:最长等待时间
线程示例1
import threading
from time import sleep
import os
a = 1
#  线程函数
def music():
    global a
    print("a =",a)
    a = 10000
    for i in range(3):
        sleep(2)
        print(os.getpid(),"播放:黄河大合唱")
# 实例化线程对象
thread = threading.Thread(target=music)
# 启动线程 线程存在
thread.start()
for i in range(4):
    sleep(1)
    print(os.getpid(),"播放:葫芦娃")
# 阻塞等待分支线程结束
thread.join()
print("a:",a)
线程示例02
from threading import Thread
from time import sleep
# 带有参数的线程函数
def func(sec,name):
    print("含有参数的线程来喽")
    sleep(sec)
    print("%s 线程执行完毕"%name)
# 循环创建线程
for i in range(5):
    t = Thread(target=func,
               args=(2,),
               kwargs={"name":"T-%d"%i},
               daemon=True)
    t.start()
创建线程类
- 继承
Thread类 - 重写
__init__方法添加自己的属性,使用super()加载父类属性 - 重写
run()方法 
使用方法
from threading import Thread
from time import sleep
class MyThread(Thread):
   def __init__(self,song):
       self.song = song
       super().__init__() # 得到父类内容
   # 线程要做的事情
   def run(self):
       for i in range(3):
           sleep(2)
           print("播放:",self.song)
t = MyThread("凉凉")
t.start() # 运行run
线程同步互斥
- 线程通信方法:线程间使用全局变量进行通信
 - 共享资源:多线程都可以操作的资源称为共享资源。对共享资源的操作代码段称为临界区。
 - 影响:对共享资源的无序操作可能会带来数据的混乱,或者操作错误。此时往往需要同步互斥机制协调操作顺序。
 - 同步互斥机制
 - 同步:同步是一种协作关系,为完成操作,线程间形成一种协调,按照必要的步骤有序执行操作。
 

- 互斥:互斥是一种制约关系,当一个进程或者线程占有资源时会进行加锁处理,此时其他进程线程就无法操作该资源,直到解锁后才能操作。
 

- 线程Event
 
from threading import Event
e = Event()  创建线程event对象
e.wait([timeout])  阻塞等待e被set
e.set()  设置e,使wait结束阻塞
e.clear() 使e回到未被设置状态
e.is_set()  查看当前e是否被设置
Event使用示例
from threading import Thread, Event
msg = None  # 通信变量
e = Event()  # 事件对象
def 杨子荣():
    print("杨子荣前来拜山头")
    global msg
    msg = "天王盖地虎"
    e.set()  # 通知主线程可以判断
t = Thread(target=杨子荣)
t.start()
print("说对口令才是自己人")
e.wait()  # 阻塞等待通知
if msg == "天王盖地虎":
    print("宝塔镇河妖")
    print("确认过眼神你是对的人")
else:
    print("打死他.... 无情啊 哥哥....")
线程锁 Lock
from  threading import Lock
lock = Lock()  创建锁对象
lock.acquire() 上锁  如果lock已经上锁再调用会阻塞
lock.release() 解锁
Lock使用示例
from threading import Thread, Lock
lock = Lock() # 创建锁
a = b = 0
def value():
    while True:
        lock.acquire() # 上锁
        if a != b:
            print("a = %d,b = %d" % (a, b))
        lock.release() # 解锁
t = Thread(target=value)
t.start()
while True:
    lock.acquire()
    a += 1
    b += 1
    lock.release()
死锁
什么是死锁
- 死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁
 

死锁产生条件
- 互斥条件:指线程使用了互斥方法,使用一个资源时其他线程无法使用。
 - 请求和保持条件:指线程已经保持至少一个资源,但又提出了新的资源请求,在获取到新的资源前不会释放自己保持的资源。
 - 不剥夺条件:不会受到线程外部的干扰,如系统强制终止线程等。
 - 环路等待条件:指在发生死锁时,必然存在一个线程——资源的环形链,如T0正在等待一个T1占用的资源;T1正在等待T2占用的资源,……,Tn正在等待已被T0占用的资源
 
如何避免死锁
- 逻辑清晰,不要同时出现上述死锁产生的四个条件
 - 通过测试工程师进行死锁检测
 
"""
死锁现象演示
"""
from time import sleep
from threading import Thread,Lock
# 账户类
class Account:
    def __init__(self,id,balance,lock):
        self._id = id
        self._balance = balance
        self.lock = lock
    # 取钱
    def withdraw(self,amount):
        self._balance -= amount
    # 存钱
    def deposit(self,amount):
        self._balance += amount
    # 查看余额
    def getBalance(self):
        return self._balance
# 转账函数
def transfer(from_,to,amount):
    from_.lock.acquire()
    from_.withdraw(amount) # from_钱减少
    from_.lock.release() # 不会产生死锁
    sleep(0.1) # 网络延迟
    to.lock.acquire()
    to.deposit(amount) # to钱增加
    # from_.lock.release() # 产生死锁
    to.lock.release()
if __name__ == '__main__':
    tom = Account("Tom",5000,Lock())
    abby = Account("abby",8000,Lock())
    t1 = Thread(target=transfer,args=(tom,abby,2000))
    t2 = Thread(target=transfer,args=(abby,tom,3000))
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print("Tom:",tom.getBalance())
    print("Abby:",abby.getBalance())
GIL问题
什么是GIL问题(全局解释器锁)
- 由于python解释器设计中加入了解释器锁,导致python解释器同一时刻只能解释执行一个线程,大大降低了线程的执行效率。
 
导致后果
- 因为遇到阻塞时线程会主动让出解释器,去解释其他线程。所以python多线程在执行多阻塞任务时可以提升程序效率,其他情况并不能对效率有所提升
 
关于GIL问题的处理
- 尽量使用进程完成无阻塞的并发行为
 - 不使用c作为解释器可以用Java,C#
 - Guido的声明:http://www.artima.com/forums/flat.jsp?forum=106&thread=214235
 
结论
- GIL问题与Python语言本身并没什么关系,属于解释器设计的历史问题。
 - 在无阻塞状态下,多线程程序程序执行效率并不高,甚至还不如单线程效率。
 - Python多线程只适用于执行有阻塞延迟的任务情形。
 
线程效率对比进程实验
class Prime(Thread):
    # 判断一个数是否为质数
    @staticmethod
    def is_prime(n):
        if n <= 1:
            return False
        for i in range(2,n // 2 + 1):
            if n % i == 0:
                return False
        return True
    def __init__(self,begin,end):
        self.__begin = begin
        self.__end = end
        super().__init__()
    def run(self):
        prime = [] # 存放所有质数
        for i in range(self.__begin,self.__end):
            if Prime.is_prime(i):
                prime.append(i)
        print(sum(prime))
@timeis
def process_10():
    jobs = []
    for i in range(1,100001,10000):
        t = Prime(i,i + 10000)
        jobs.append(t)
        t.start()
    for i in jobs:
        i.join()
if __name__ == '__main__':
    process_10()
进程线程的区别联系
区别联系
- 两者都是多任务编程方式,都能使用计算机多核资源
 - 进程的创建删除消耗的计算机资源比线程多
 - 进程空间独立,数据互不干扰,有专门通信方法;线程使用全局变量通信
 - 一个进程可以有多个分支线程,两者有包含关系
 - 多个线程共享进程资源,在共享资源操作时往往需要同步互斥处理
 - Python线程存在GIL问题,但是进程没有。
 
使用场景

- 任务场景:一个大型服务,往往包含多个独立的任务模块,每个任务模块又有多个小独立任务构成,此时整个项目可能有多个进程,每个进程又有多个线程。
 - 编程语言:Java,C#之类的编程语言在执行多任务时一般都是用线程完成,因为线程资源消耗少;而Python由于GIL问题往往使用多进程。
 
 
           最后一次更新于2022-12-01 16:32         
 
        
Alipay
Wechat
           
           
   
  
0 条评论