1. 戏说线程和进程 对于新手来说,首先要理解线程的概念,以及为什么需要线程编程。 什么是线程呢? 网上一般是这样定义的: 线程(thread)是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。 哈哈,你听懂了吗? 我觉得这样的定义纯粹是自说自话: 新手看完了一脸懵逼,老鸟看完了不以为然。 咱们还是用白话解释一下吧:

  • 假定你经营着一家物业管理公司。最初,业务量很小,事事都需要你亲力亲为,给老张家修完暖气管道,立马再去老李家换电灯泡——这叫单线程,所有的工作都得顺序执行。
  • 后来业务拓展了,你雇佣了几个工人,这样,你的物业公司就可以同时为多户人家提供服务了——这叫多线程,你是主线程。
  • 工人们使用的工具,是物业管理公司提供的,大家共享——这叫多线程资源共享。
  • 工人们在工作中都需要管钳,可是管钳只有一把——这叫冲突。解决冲突的办法有很多,比如排队、等同事用完后的微信通知等——这叫线程同步。
  • 业务不忙的时候,你就在办公室喝喝茶。下班时间一到,你群发微信,所有的工人不管手头的工作是否完成,都立马撂下工具,跟你走人。因此如果有必要,你得避免不要在工人正忙着的时候发下班的通知——这叫线程守护属性设置和管理。
  • 再后来,你的公司规模扩大了,同时为很多生活社区服务,你在每个生活社区设置了分公司,分公司由分公司经理管理,运营机制和你的总公司几乎一模一样——这叫多进程,总公司叫主进程,分公司叫子进程。
  • 总公司以及各个分公司之间,工具都是独立的,不能借用、混用——这叫进程间不能共享资源。各个分公司之间可以通过专线电话联系——这叫管道。各个分公司之间还可以通过公司公告栏交换信息——这叫共享内存。
  • 分公司可以跟着总公司一起下班,也可以把当天的工作全部做完之后再下班——这叫守护进程设置。


Python 提供了多个模块来支持多线程编程,包括 thread、 threading 和 Queue 模块等。 程序是可以使用 thread 和 threading 模块来创建与管理线程。 thread 模块提供了基本的线程和锁定支持; 而 threading 模块提供了更高级别、功能更全面的线程管理。 我们在这里只讨论 threading 模块。 2. 创建并使用线程  

使用 threading 模块的 Thread 类,可以快速创建并启动线程。 当然,创建线程之前,你得先把交给线程去做的工作,写成一个函数,我们管这个函数叫线程函数。 threading.Thread 类有以下方法和属性:

对象

描述

name

线程(属性名)

ident

线程标识符(属性)

daemon

线程是否是守护线程(属性)

start()

开启线程

join()

等待至线程终止,或超过参数指定的时间

setDaemon()

设置线程是否是守护线程

getName()

返回线程名

isAlive()

判断线程是否还在运行

isDaemon()

判断线程是否是守护线程

run()

定义线程功能的方法(通常在子类中被应用开发者重写)

我们设计一个任务:你(主线程)启动3个子线程,名字分别是A、B、C。其中A线程启动后,你要先观察5秒钟,再启动其他线程。每个子线程的任务是每隔指定时间间隔就向你问好,并报上自己的名字,你呢,只管睡觉。30秒后,你醒了。你逐一检查了各个子线程的工作状态之后,结束运行。下面是实现代码:

import timeimport threadingdef hello(name, t):    """线程函数"""    for i in range(10):        print('Hello, 我是小%s'%name)        time.sleep(t)def demo():    A = threading.Thread(target=hello, args=('A',1), name='A')    B = threading.Thread(target=hello, args=('B',2), name='B')    C = threading.Thread(target=hello, args=('C',3), name='C')    #C.setDaemon(True) # 设置子线程在主线程结束时是否无条件跟随主线程一起退出    A.start()    A.join(5) # 等待A线程结束,若5秒钟后未结束,则代码继续    B.start()    C.start()    time.sleep(20)    print('进程A%s'%('还在工作中' if A.isAlive() else '已经结束工作',))    print('进程B%s'%('还在工作中' if B.isAlive() else '已经结束工作',))    print('进程C%s'%('还在工作中' if C.isAlive() else '已经结束工作',))    print('下班了。。。')if __name__ == '__main__':    demo()


但是,运行这段代码,你会发现,当你喊下班的时候,小C并没有立刻撂下手头的活儿跟你走人,而是做完了工作之后才跟你走人——或者说,是你在等他做完工作后一起走人。 这里容易产生误会,以为主线程结束后,子线程还会工作到任务完成。 这是错误的理解。 真相是,主线程不忍心打断正在忙碌的子线程(active),一旦该子线程休眠(inactive),不管任务是否结束,都会被主线程直接带走。 那么如何令子线程在主线程结束时无条件跟随主线程一起走人呢? 很简单,在线程 start() 之前,使用 setDaemon(True) 设置该线程为守护线程就可以了。 子线程的 daemon 属性默认为 False。 3. 线程同步

3.1 线程锁 Lock


前几天,我想在一个几百人的微信群里统计喜欢吃苹果的人数。有人说,咱大家从1开始报数吧,并敲了起始数字1,立马有人敲了数字2,3。但是统计很快就进行不下去了,因为大家发现,有好几个人敲4,有更多的人敲5。


这就是典型的资源竞争冲突: 统计用的计数器就是唯一的资源,很多人(子线程)都想取得写计数器的资格。 怎么办呢? Lock(互斥锁)就是一个很好的解决方案。 Lock只能有一个线程获取,获取该锁的线程才能执行,否则阻塞; 执行完任务后,必须释放锁。 请看演示代码:

# -*- encoding: utf8 -*-import timeimport threadinglock = threading.Lock() # 创建互斥锁counter = 0 # 计数器def hello():    """线程函数"""    global counter    if lock.acquire(): # 请求互斥锁,如果被占用,则阻塞,直至获取到锁        time.sleep(0.2) # 假装思考、敲键盘需要0.2秒钟        counter += 1        print('我是第%d个'%counter)    lock.release() # 千万不要忘记释放互斥锁,否则后果很严重def demo():    threads = list()    for i in range(30): # 假设群里有30人,都喜欢吃苹果        threads.append(threading.Thread(target=hello))        threads[-1].start()    for t in threads:        t.join()    print('统计完毕,共有%d人'%counter)if __name__ == '__main__':    demo()


除了互斥锁,线程锁还有另一种形式,叫做递归锁(RLock),又称可重入锁。 已经获得递归锁的线程可以继续多次获得该锁,而不会被阻塞,释放的次数必须和获取的次数相同才会真正释放该锁。 欲了解详情,同学们可以自行检索资料。

3.2 信号量 Semaphore


上面的例子中,统计用的计数器是唯一的资源,因此使用了只能被一个线程获取的互斥锁。 假如共享的资源有多个,多线程竞争时一般使用信号量(Semaphore)同步。 信号量有一个初始值,表示当前可用的资源数,多线程执行过程中会通过 acquire() 和 release() 操作,动态的加减信号量。 比如,有30个工人都需要电锤,但是电锤总共只有5把。 使用信号量(Semaphore)解决竞争的代码如下: 3.3 事件Event

想象我们每天早上上班的场景: 为了不迟到,总得提前几分钟(我一般都会提前30分钟)到办公室,打卡之后,一看表,还不到工作时间,大家就看看新闻、聊聊天啥的; 工作时间一到,立马开工。 如果有人迟到了呢,自然就不能看新闻聊天了,得立即投入工作中。 这个场景中,每个人代表一个线程,工作时间到,表示事件(Event)发生。 事件发生前,线程会调用 wait() 方法阻塞自己(对应看新闻聊天),一旦事件发生,会唤醒所有调用 wait() 而进入阻塞状态的线程。

# -*- encoding: utf8 -*-import timeimport threadingE = threading.Event() # 创建事件def work(id):    """线程函数"""    print('上班打卡'%id)    if E.is_set(): # 已经到点了        print('迟到了'%id)    else: # 还不到点        print('浏览新闻中...'%id)        E.wait() # 等上班铃声    print('开始工作了...'%id)    time.sleep(10) # 工作10秒后下班    print('下班了'%id)def demo():    E.clear() # 设置为“未到上班时间”    threads = list()    for i in range(3): # 3人提前来到公司打卡        threads.append(threading.Thread(target=work, args=(i,)))        threads[-1].start()    time.sleep(5) # 5秒钟后上班时间到    E.set()    time.sleep(5) # 5秒钟后,大佬(9号)到    threads.append(threading.Thread(target=work, args=(9,)))    threads[-1].start()    for t in threads:        t.join()    print('都下班了,关灯关门走人')if __name__ == '__main__':    demo()


3.4 条件 Condition

两位小朋友,Hider 和 Seeker,打算玩一个捉迷藏的游戏,规则是这样的: Seeker 先找个眼罩把眼蒙住,喊一声“我已经蒙上眼了”; 听到消息后,Hider 就找地方藏起来,藏好以后,也要喊一声“我藏好了,你来找我吧”; Seeker 听到后,也要回应一声“我来了”,捉迷藏正式开始。 各自随机等了一段时间后,两位小朋友都憋住了跑了出来。 谁先跑出来,就算谁输。

# -*- encoding: utf8 -*-import timeimport threadingimport randomcond = threading.Condition() # 创建条件对象draw_Seeker = False # Seeker小朋友认输draw_Hidwer = False # Hider小朋友认输def seeker():    """Seeker小朋友的线程函数"""    global draw_Seeker, draw_Hidwer    time.sleep(1) # 确保Hider小朋友已经进入消息等待状态    cond.acquire() # 阻塞时请求资源    time.sleep(random.random()) # 假装蒙眼需要花费时间    print('Seeker: 我已经蒙上眼了')    cond.notify() # 把消息通知到Hider小朋友    cond.wait() # 释放资源并等待Hider小朋友已经藏好的消息    print('Seeker: 我来了') # 收到Hider小朋友已经藏好的消息后    cond.notify() # 把消息通知到Hider小朋友    cond.release() # 不要再听消息了,彻底释放资源    time.sleep(random.randint(3,10)) # Seeker小朋友的耐心只有3-10秒钟    if draw_Hidwer:        print('Seeker: 哈哈,我找到你了,我赢了')    else:        draw_Seeker = True        print('Seeker: 算了,我找不到你,我认输啦')def hider():    """Hider小朋友的线程函数"""    global draw_Seeker, draw_Hidwer    cond.acquire() # 阻塞时请求资源    cond.wait() # 如果先于Seeker小朋友请求到资源,则立刻释放并等待    time.sleep(random.random()) # 假装找地方躲藏需要花费时间    print('Hider: 我藏好了,你来找我吧')    cond.notify() # 把消息通知到Seeker小朋友    cond.wait() # 释放资源并等待Seeker小朋友开始找人的消息    cond.release() # 不要再听消息了,彻底释放资源    time.sleep(random.randint(3,10)) # Hider小朋友的耐心只有3-10秒钟    if draw_Seeker:        print('Hider: 哈哈,你没找到我,我赢了')    else:        draw_Hidwer = True        print('Hider: 算了,这里太闷了,我认输,自己出来吧')def demo():    th_seeker = threading.Thread(target=seeker)    th_hider = threading.Thread(target=hider)    th_seeker.start()    th_hider.start()    th_seeker.join()    th_hider.join()if __name__ == '__main__':    demo()


python判断子线程是否结束 python子线程阻塞_c++ 获取线程id