功能强大的python包(十一):threading (多线程)

1.threading简介

threading库是python的线程模型,利用threading库我们可以轻松实现多线程任务。

2.进程与线程简介

在这里插入图片描述

通过上图,我们可以直观的总结出进程、线程及其之间的关系与特点:

  • 进程是资源分配的最小单元,一个程序至少包含一个进程
  • 线程是程序执行的最小单元,一个进程至少包含一个线程
  • 每个进程都有自己独占的地址空间、内存、数据栈等;由于进程间的资源独立,所以进程间通信(IPC)是多进程的关键问题
  • 同一进程下的所有线程都共享该进程的独占资源,由于线程间的资源共享,所有数据同步与互斥是多线程的难点

此外,多进程多线程也必须考虑硬件资源的架构:

  • 单核CPU中每个进程中同时刻只能运行一个线程,只在多核CPU中才能够实现线程并发
  • 当线程需运行但运行空间不足时,先运行高优先级线程,后运行低优先级线程
  • Windows系统新开进行会增加很大的开销,因此Windows系统下使用多线程更具优势,这时更应该考虑的问题是资源同步和互斥
  • Linux系统新开进程的开销增加很小,因此Linux系统下使用多进程更具优势,这时更应该考虑的问题是进程间通信

3.python多线程特点

python的多线程并非真正意义上的多线程,由于全局解释器锁(GIL)的存在,python中的线程只有在获得GIL后才拥有运行权限,而GIL同一时刻只能被一个线程所拥有。


在这里插入图片描述

python在运行计算密集型的多线程程序时,更倾向于让线程在整个时间片内始终占据GIL,而I/O密集型的多线程程序在I/O被调用前会释放GIL,允许其他线程在I/O执行时获得GIL,因此python的多线程更适用于I/O密集型的程序(网络爬虫)。

4.threading实现多线程

threading

线程创建
image

在这里插入图片描述

守护线程是指在程序运行时在后台提供一种通用服务的线程,比如垃圾回收线程;这种线程在程序中并非不可或缺。

默认创建的线程为非守护线程,等所有的非守护线程结束后,程序也就终止了,同时杀死进程中的所有守护线程。

只要非守护线程还在运行,程序就不会终止。

import threading
import time

#将创建的函数传递进threading.Thread()对象
def func():
    print(threading.current_thread().getName())  
t1 = threading.Thread(target=func,name='test_threading_1')
t1.start()
t1.join()

#继承threading.Thread类,改写run()方法
class TestThread(threading.Thread):
    def __init__(self,name):
        super(TestThread,self).__init__()
        self.name = name
    def run(self):
        print(f'线程{self.name}正在进行!')
        n = 0
        while n < 5:
            n += 1
            print(f'线程{self.name}>>>{n}')
            time.sleep(1)
        print(f'线程{self.name}结束运行')
t1 = TestThread('thread-1')
t2 = TestThread('thread-2')
t1.start()
t2.start()


锁对象:threading.Lock
在这里插入图片描述

锁被用来实现对共享资源的同步访问;通过为每一个共享资源创建一个Lock对象,我们需要访问该资源时只能通过条用acquire方法来获取锁对象,待资源访问结束后,调用release方法释放Lock对象。

from threading import Thread,Lock
import time

lock = Lock()

def func():
    global n
    #加锁
    lock.acquire()
    team = n
    time.sleep(1)
    n = team-1
    #释放锁
    lock.release()
    
if __name__ == '__main__':
    n = 100
    l = []
    for i in range(100):
        t = Thread(target=func)
        l.append(t)
        t.start()
        
    for t in l:
        #将t线程设置成阻塞状态,直到t线程执行完后才能进入下一线程
        t.join()
    print(n)
from threading import Thread,Lock

x = 0
lock = Lock()

def func():
    global x
    lock.acquire()
    for i in range(6000):
        x = x+1
    lock.release()
    
if __name__ == '__main__':
    t1 = Thread(target=func)
    t2 = Thread(target=func)
    t3 = Thread(target=func)
    
    t1.start()
    t2.start()
    t3.start()
    
    t1.join()
    t2.join()
    t3.join()
    print(x)

递归锁对象:threading.RLock
在这里插入图片描述

在应用锁对象时,会发生死锁;死锁是指两个或两个以上的线程在执行过程中,因争夺资源访问权而造成的互相等待的现象,从而一直僵持下去。

递归锁对象就是用来解决死锁问题的,RLock对象内部维护着一个Lock和一个counter变量,counter记录着acquire的次数,从而使得资源可以被多次acquire,直到一个线程的所有acquire都被release时,其他线程才能够acquire资源。

from threading import Thread,RLock,currentThread
import time

rlock1 = rlock2 = RLock()


class Test(Thread):
    
    def run(self):
        self.task1()
        self.task2()
        
    def task1(self):
        rlock1.acquire()
        print(f'{self.name}获得锁1')
        rlock2.acquire()
        print(f'{self.name}获得锁2')
        rlock2.release()
        print(f'{self.name}释放锁2')
        rlock1.release()
        print(f'{self.name}释放锁1')
        
    def task2(self):
        rlock2.acquire()
        print(f'{self.name}获得锁2')
        time.sleep(1)
        rlock1.acquire()
        print(f'{self.name}获得锁1')
        rlock1.release()
        print(f'{self.name}释放锁1')
        rlock2.release()
        print(f'{self.name}释放锁2')
    
for i in range(3):
    t = Test()
    t.start()
              

条件变量对象:threading.Condition
在这里插入图片描述
import threading
import time

condition_lock = threading.Condition()

PRE = 0

def pre():
    print(PRE)
    return PRE

def test_thread_hi():
    condition_lock.acquire()
    print('wait for the commend of test_thread_hello')
    condition_lock.wait_for(pre)
    print('contain doing')
    condition_lock.release()
    
def test_thread_hello():
    time.sleep(1)
    condition_lock.acquire()
    global PRE
    PRE = 1
    print('change PRE to 1')
    print('tell the test_thread_hi to acquire lock')
    condition_lock.notify()
    condition_lock.release()
    print('you need lock?')
    
def main():
    thread_hi = threading.Thread(target=test_thread_hi)
    thread_hello = threading.Thread(target=test_thread_hello)
    thread_hi.start()
    thread_hello.start()
    
if __name__ == '__main__':
    main()
    

信息量对象:threading.Semaphore
在这里插入图片描述

一个信号量管理一个内部计数器,acquire( )方法会减少计数器,release( )方法则增加计算器,计数器的值永远不会小于零,当调用acquire( )时,如果发现该计数器为零,则阻塞线程,直到调用release( ) 方法使计数器增加。

import threading
import time

semaphore4 = threading.Semaphore(4)

def thread_semaphore(index):
    semaphore4.acquire()
    time.sleep(2)
    print('thread_%s is runing'%index)
    semaphore4.release()
    
def main():
    for index in range(9):
        threading.Thread(target=thread_semaphore,args=(index,)).start()
        
if __name__ == '__main__':
    main()

事件对象:threading.Event

如果一个或多个线程需要知道另一个线程的某个状态才能进入下一步的操作,就可以使用线程的event事件对象来处理。


在这里插入图片描述
import threading
import time

event = threading.Event()

def student_exam(student_id):
    print('学生%s等监考老师发卷'%student_id)
    event.wait()
    print('开始考试了')
    
def invigilate_teacher():
    time.sleep(3)
    print('考试时间到了,学生们可以考试了')
    event.set()
    
def main():
    for student_id in range(5):
        threading.Thread(target=student_exam,args=(student_id,)).start()
    threading.Thread(target=invigilate_teacher).start()
    
if __name__ == '__main__':
    main()

定时器对象:threading.Timer

表示一个操作需要在等待一段时间之后执行,相当于一个定时器。

在这里插入图片描述

栅栏对象:threading.Barrier
在这里插入图片描述

5.写在最后

虽然在python的编程世界里很少需要我们用到多线程技术,但能够掌握多线程与锁机制的思想,将利于我们走通一条更广的编程之路!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容