python之多线程


进程的概念:以一个整体的形式暴露给操作系统管理,里面包含各种资源的调用。 对各种资源管理的集合就可以称为进程。
线程的概念:是操作系统能够进行运算调度的最小单位。本质上就是一串指令的集合。

进程和线程的区别:
1、线程共享内存空间,进程有独立的内存空间。
2、线程启动速度快,进程启动速度慢。注意:二者的运行速度是无法比较的。
3、线程是执行的指令集,进程是资源的集合
4、两个子进程之间数据不共享,完全独立。同一个进程下的线程共享同一份数据。
5、创建新的线程很简单,创建新的进程需要对他的父进程进行一次克隆。
6、一个线程可以操作(控制)同一进程里的其他线程,但是进程只能操作子进程
7、同一个进程的线程可以直接交流,两个进程想要通信,必须通过一个中间代理来实现。
8、对于线程的修改,可能会影响到其他线程的行为。但是对于父进程的修改不会影响到子进程。


  • 实例一:最简单的多线程
    在python3中,我们使用threading模块来支持多线程。
    t1=threading.Thread(target=run,args=("t1",)) 创建一个线程实例
    t1.start() 启动这个线程实例
    t1.join() 不加join的话,主线程和子线程完全是并行的,加了join主线程得等这个子线程执行完毕,才能继续往下走。这样才能得到这个程序真正的运行时间。
    观察实验结果,我们得到原本需要4s左右的执行时间变成了2,秒,证明了多线程的高效率。
import threading,time
def run(n):
    print("task  ",n)
    time.sleep(2)

start_time=time.time()
t1=threading.Thread(target=run,args=("t1",))
t2=threading.Thread(target=run,args=("t2",))

t1.start()
t2.start()
t1.join()
t2.join()
print(time.time()-start_time)


F:\anaconda\python.exe F:/web/s14/进程、线程/noke.py
task   t1
task   t2
2.0011146068573

  • 实例二:继承式调用多线程
    创建一个新的类来支持多线程,继承了threading.Thread类
import threading,time
class MyThread(threading.Thread):
    def __init__(self,n,sleep_time):
        super(MyThread, self).__init__()
        self.n=n
        self.sleeptime=sleep_time
    def run(self):
        print("run task",self.n)
        time.sleep(2)
        print("task done,",self.n)


t1=MyThread("t1",2)
t2=MyThread("t2",4)


t1.start()
t2.start()
t1.join()  #wait()  第一个线程执行完毕后再执行第二个线程
t2.join()




F:\anaconda\python.exe F:/web/s14/进程、线程/threading_ex2.py
run task t1
run task t2
task done, t1
task done, t2
main thread!
  • 实例三: 一次性启动多个线程

第一个程序,使用循环来创建线程,但是这个程序中一共有51个线程,我们创建了50个线程,但是还有一个程序本身的线程,是主线程。这51个线程是并行的。注意:这个程序中是主线程启动了子线程。

import threading,time
def run(n):
    print("task ",n)
    time.sleep(2)


for i in range(50):
    t=threading.Thread(target=run,args=("t- %s"%i,))
    t.start()

相比上个程序,这个程序多了一步计算时间,但是我们观察结果会发现,程序显示的执行时间只有0.007秒,这是因为最后一个print函数它存在于主线程,而整个程序主线程和所有子线程是并行的,那么可想而知,在子线程还没有执行完毕的时候print函数就已经执行了,总的来说,这个时间只是执行了一个线程也就是主线程所用的时间。

import threading,time
def run(n):
    print("task ",n)
    time.sleep(2)

start_time=time.time()
for i in range(50):
    t=threading.Thread(target=run,args=("t- %s"%i,))
    t.start()


print("cost  :" ,time.time()-start_time)


实验部分结果:
cost  : 0.00700068473815918

接下来这个程序,吸取了上面这个程序的缺点,创建了一个列表,把所有的线程实例都存进去,然后使用一个for循环依次对线程实例调用join方法,这样就可以使得主线程等待所创建的所有子线程执行完毕才能往下走。注意实验结果:和两个线程的结果都是两秒多一点

import threading,time
def run(n):
    print("task ",n)
    time.sleep(2)

start_time=time.time()
t_obj=[]
for i in range(50):
    t=threading.Thread(target=run,args=("t- %s"%i,))
    t_obj.append(t)
    t.start()

for t in t_obj:
    t.join()


print("cost  :" ,time.time()-start_time)

部分实验结果:
cost  : 2.0061144828796387

注意观察实验结果,并没有执行打印task has done,并且程序执行时间极其短。
这是因为在主线程启动子线程前把子线程设置为守护线程。
只要主线程执行完毕,不管子线程是否执行完毕,就结束。但是会等待非守护线程执行完毕
主线程退出,守护线程全部强制退出。皇帝死了,仆人也跟着殉葬
应用的场景 : socket-server

import threading
import time
def run(n):
    print("task",n)
    time.sleep(2)
    print("task has done!")     
start_time=time.time()
for i in range(50):
    t=threading.Thread(target=run,args=("t-%s"%i,))
    t.setDaemon(True)  #把当前线程设置为守护线程,一定在start前设置
    t.start()
print(threading.current_thread(),threading.active_count())
print(time.time()-start_time)  

部分实验结果:
task t-43
task t-44
task t-45
task t-46
task t-47
task t-48
task t-49
<_MainThread(MainThread, started 5940)> 51
0.0060002803802490234

  • 实例四:线程锁(互斥锁Mutex)
    介绍一下python的GIL,全局解释器锁。并不是python的特性,是实现python解析器的时候引入的概念。这个锁是为了保证同一份数据不能被多个线程同时修改。因为cpython是使用c封装的,所以线程也是用c语言实现的,在和cpu交互的时候使用的c接口。(java,c++等语言的自己实现的线程,所以自己可以直接控制cpu),而python就创造了一个全局解释器锁,来保证同一份数据不能被多个线程同时修改。
    所以,这就造成了python的一个缺陷,无论多少核的机器,同一时刻只能有一个线程在执行。jpython没有这个问题。
    python的未来是pypy。


注意:gil只是为了减低程序开发复杂度。但是在2.几的版本上,需要加用户态的锁(gil的缺陷)而在3点几的版本上,加锁不加锁都一样。

import time
import threading
def run():
    lock.acquire()    #修改数据前加锁
    global num
    num +=1
    lock.release()    #修改完后释放
lock=threading.Lock()
num=0
t_objs = []
for i in range(1000):
    t=threading.Thread(target=run)
    t.start()
    t_objs.append(t)
for t in t_objs:  #循环线程实例列表,等待子线程执行完毕
    t.join()
print(num)         
  • 实例五:Rlock递归锁
    大锁中包含两个并行的子锁。
    普通的锁,在多个锁的情况下,无法找到对应的钥匙,而使用Rlock可以,类似于字典的原理。
import threading, time
def run1():
    print("grab the first part data")
    lock.acquire()
    global num
    num += 1
    lock.release()
    return num


def run2():
    print("grab the second part data")
    lock.acquire()
    global num2
    num2 += 1
    lock.release()
    return num2


def run3():
    lock.acquire()
    res = run1()
    print('--------between run1 and run2-----')
    res2 = run2()
    lock.release()
    print(res, res2)


if __name__ == '__main__':

    num, num2 = 0, 0
    lock = threading.RLock()  #RLOCK
    for i in range(10):
        t = threading.Thread(target=run3)
        t.start()

while threading.active_count() != 1:
    pass
else:
    print('----all threads done---')
    print(num, num2)
  • 实例六:信号量
    互斥锁,同时允许一个线程更改数据,而信号量同时允许一定数量的线程更改数据
import threading, time


def run(n):
    semaphore.acquire()
    time.sleep(1)
    print("run the thread: %s\n" % n)
    semaphore.release()


if __name__ == '__main__':


    semaphore = threading.BoundedSemaphore(5)  # 最多允许5个线程同时运行
    for i in range(10):
        t = threading.Thread(target=run, args=(i,))
        t.start()

while threading.active_count() != 1:
    pass
else:
    print('----all threads done---')

  • 实例七:event
    通过event来实现两个或者多个线程之间的交互。
    通过全局变量的设置
    下面这个例子,一个红绿灯线程,多个车线程,车辆按红绿灯规则行驶。
import time,threading

event=threading.Event()
def lighter():
    count=0
    event.set()     
    while True:
        if count > 5 and count <10:
            event.clear()
            print("\033[41;1mred light is on ...\033[0m")
        elif count > 10:
            event.set()
            print("\033[42;1mgreen light is on ...\033[0m")
            count=0
        else:
            print("\033[42;1mgreen light is on ...\033[0m")
        time.sleep(1)
        count+=1
def car(name):
    while True:
        if event.is_set():
            print("[%s] running ......"%name )
            time.sleep(1)
        else:
            print("[%s]  sees red light.."%name)
            event.wait()
            print("[%s] green light is on, start going...")




light = threading.Thread(target=lighter,)
light.start()


car1=threading.Thread(target=car,args=("tesla",))
car1.start()
  • 实例八:queue队列
    队列:非常重要
    功能:
    1、提高效率
    2、完成了程序的解耦
    可以理解为一个容器,这个容器里面存放数据
    既然有列表、元组这种容器为什么需要队列?
    区别:列表中取数据,相当于复制一份数据,而队列中,数据只有一份,取走了就没了
    先入先出,后进先出,优先级队列

下面这个程序是一个典型的生产者消费者模型。
生产者消费者模型是经典的在开发架构中使用的模型
运维中的集群就是生产者消费者模型,生活中很多都是

import threading
import queue,time

q=queue.Queue(maxsize=10)
def Producer(name):
    count=1
    while True:
        q.put("骨头 %s"%count)
        print("生产了骨头",count)
        count+=1
        time.sleep(1)      
def Consumer(name):
    while True:
        print("[%s] 取到  [%s] 并且吃了它。。。"%(name,q.get()))
        time.sleep(1)
p=threading.Thread(target=Producer,args=('cq',))
c=threading.Thread(target=Consumer,args=("dog",))
c1=threading.Thread(target=Consumer,args=("cc",))

p.start()
c.start()
c1.start()

综上:

那么,多线程的使用场景是什么?
python中的多线程实质上是对上下文的不断切换,可以说是假的多线程。而我们知道,io操作不占用cpu,计算占用cpu,那么python的多线程适合io操作密集的任务,比如socket-server,那么cpu密集型的任务,python怎么处理?python可以折中的利用计算机的多核:启动八个进程,每个进程有一个线程。这样就可以利用多进程解决多核问题。

下一篇将介绍多进程。

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

推荐阅读更多精彩内容

  • 一. 操作系统概念 操作系统位于底层硬件与应用软件之间的一层.工作方式: 向下管理硬件,向上提供接口.操作系统进行...
    月亮是我踢弯得阅读 5,948评论 3 28
  • 进程和线程 进程 所有运行中的任务通常对应一个进程,当一个程序进入内存运行时,即变成一个进程.进程是处于运行过程中...
    胜浩_ae28阅读 5,084评论 0 23
  • 线程 操作系统线程理论 线程概念的引入背景 进程 之前我们已经了解了操作系统中进程的概念,程序并不能单独运行,只有...
    go以恒阅读 1,630评论 0 6
  • 进程与线程的区别 现在,多核CPU已经非常普及了,但是,即使过去的单核CPU,也可以执行多任务。由于CPU执行代码...
    苏糊阅读 754评论 0 2
  • 顾名思义,进程即正在执行的一个过程。进程是对正在运行程序的一个抽象。进程的概念起源于操作系统,是操作系统最核心的概...
    SlashBoyMr_wang阅读 1,120评论 0 2