zoukankan      html  css  js  c++  java
  • python之进程,线程

    什么是进程(process)?

    程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程。程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念。

    在多道编程中,我们允许多个程序同时加载到内存中,在操作系统的调度下,可以实现并发地执行。这是这样的设计,大大提高了CPU的利用率。进程的出现让每个用户感觉到自己独享CPU,因此,进程就是为了在CPU上实现多道编程而提出的

    有了进程为什么还要线程?

    进程有很多优点,它提供了多道编程,让我们感觉我们每个人都拥有自己的CPU和其他资源,可以提高计算机的利用率。很多人就不理解了,既然进程这么优秀,为什么还要线程呢?其实,仔细观察就会发现进程还是有很多缺陷的,主要体现在两点上:

    • 进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。

    • 进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。

    例如,我们在使用qq聊天, qq做为一个独立进程如果同一时间只能干一件事,那他如何实现在同一时刻 即能监听键盘输入、又能监听其它人给你发的消息、同时还能把别人发的消息显示在屏幕上呢?你会说,操作系统不是有分时么?但我的亲,分时是指在不同进程间的分时呀, 即操作系统处理一会你的qq任务,又切换到word文档任务上了,每个cpu时间片分给你的qq程序时,你的qq还是只能同时干一件事呀。

    再直白一点, 一个操作系统就像是一个工厂,工厂里面有很多个生产车间,不同的车间生产不同的产品,每个车间就相当于一个进程,且你的工厂又穷,供电不足,同一时间只能给一个车间供电,为了能让所有车间都能同时生产,你的工厂的电工只能给不同的车间分时供电,但是轮到你的qq车间时,发现只有一个干活的工人,结果生产效率极低,为了解决这个问题,应该怎么办呢?。。。。没错,你肯定想到了,就是多加几个工人,让几个人工人并行工作,这每个工人,就是线程!

    什么是线程(thread)?

    线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务

    进程想要执行,就必须先生成一个线程,让线程执行,进程只是把资源整合,不能执行

    所有再同一个进程里的线程是共享同一块内存空间的

    进程与线程的区别?

    启动一个线程要比启动进程快,但是运行速度是没有办法比的。

    1,线程共享内存空间,进程的内存是独立的

    2,两个子进程之间的数据的分开的,不是共享的,同一个进程中的两个线程是共享的数据

    3,同一个进程的线程之间可以直接交流,进程之间想通信必须通过一个中间代理来实现

    4,创建新线程很简单,创建新进程需要对其父进程进行一次克隆

    5,一个线程可以控制和操作同一进程里的其他线程,但是进程只能操作子进程

    多线程的实际例子:

    #--------------------------这是一种多线程的写法,直接调用--------------------
    import threading
    import time
    def run(n):
    print('task',n)
    time.sleep(2)
    t1 = threading.Thread(target=run,args=('t1',)) #target是标题的意思,调用哪个方法,就写哪个方法名字,args('t1',)这是传参数
    t2 = threading.Thread(target=run,args=('t2',))
    t1.start() #启动线程
    t2.start()
    '''
    这两个线程是并行的,一共等2秒钟
    '''
    #--------------------------这是另外一种多线程的写法,继承式写法--------------------
    import threading
    import time

    class MyThreading(threading.Thread):
    def __init__(self,n):
    threading.Thread.__init__(self)
    self.n = n

    def run(self): #必须要叫run 定义每个线程要运行的函数
    print('running task',self.n)
    time.sleep(2)

    t1 = MyThreading('t1')
    t2 = MyThreading('t2')
    t1.start()
    t1.join() #相当于wait,python里面有join
    t2.start()
    #--------------------------再次理解多线程运行的原理--------------------
    import threading
    import time

    class MyThreading(threading.Thread):
    def __init__(self,n,sleep_lime): #这里把父类的构造函数重写了,所以要重构一下,也就是继承一下
    threading.Thread.__init__(self) #这里把父类的构造函数重写了,所以要重构一下,也就是继承一下
    self.n = n
    self.sleep_time = sleep_lime

    def run(self): #必须要叫run 定义每个线程要运行的函数,线程运行的时候,就是调用run,其他的不调用,只是再这种写法里面
    print('running task',self.n)
    time.sleep(self.sleep_time)
    print('task done',self.n)

    t1 = MyThreading('t1',2)
    t2 = MyThreading('t2',4)
    t1.start() #调用了run方法
    #t1.join() #相当于wait,python里面有join,等待
    t2.start() #调用了run方法
    t1.join() #这是t1的线程等待,程序运行从上往下,运行到t1.join()的时候,等t1线程,此时t2线程继续调用run方法运行,t1时间到了后,程序继续从上往下走,运行print('main...........')
    #等print('main...........')运行完毕,也意味着主程序(主线程)运行完成,此时t2的等待时间到了之后,就输出t2调用run里面的东西了
    #t2.join()
    print('main...........',threading.current_thread()) #这个threading.current_thread()是查看这句代码是哪个线程执行的
    print(threading.active_count()) #threading.active_count()这个是查看当前线程活动的个数

    #-----------------------------守护进程---------------------------------------------
    主进程创建守护进程
    其一:守护进程会在主进程代码执行结束后就终止
    其二:守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes are not allowed to have children
    注意:进程之间是互相独立的,主进程代码运行结束,守护进程随即终止
    import time
    def run(n):
    print('task',n)
    time.sleep(2)
    print('task done',n)
    #启动50个线程
    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('all threading')
    print('cost:',time.time()-start_time)

    另外一个例子:
    from multiprocessing import Process
    import time
    import random

    class Piao(Process):
    def __init__(self,name):
    self.name=name
    super().__init__()
    def run(self):
    print('%s is piaoing' %self.name)
    time.sleep(random.randrange(1,3))
    print('%s is piao end' %self.name)


    p=Piao('egon')
    p.daemon=True #一定要在p.start()前设置,设置p为守护进程,禁止p创建子进程,并且父进程代码执行结束,p即终止运行
    p.start()
    print('主')


    Python GIL(Global Interpreter Lock)全局解析器锁

    首先需要明确的一点是GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。就好比C++是一套语言(语法)标准,但是可以用不同的编译器来编译成可执行代码。有名的编译器例如GCC,INTEL C++,Visual C++等。Python也一样,同样一段代码可以通过CPython,PyPy,Psyco等不同的Python执行环境来执行。像其中的JPython就没有GIL。然而因为CPython是大部分环境下默认的Python执行环境。所以在很多人的概念里CPython就是Python,也就想当然的把GIL归结为Python语言的缺陷。所以这里要先明确一点:GIL并不是Python的特性,Python完全可以不依赖于GIL

    这篇文章透彻的剖析了GIL对python多线程的影响,强烈推荐看一下:http://www.dabeaz.com/python/UnderstandingGIL.pdf 

    GIL:多核的意义就是同时做很多事情,但是python中不是这样的,无论你有几核,python运行起来就是单个线程运行,叫假线程。这就叫全局解析器锁
    python其实就是调用操作系统原生的线程接口,只能启一个。python调用一个线程的时候,就要把上下文关系传给他。同一时间只能有一个线程去工作,
    这是全局锁控制的,java就能自定义执行。以后pypy会是重点,因为取消了这个限制,而且还是计时编译。

    #----------------------------线程锁(互斥锁Mutex)--------------------------
    #一个进程下可以启动多个线程,多个线程共享父进程的内存空间,也就意味着每个线程可以访问同一份数据,此时,如果2个线程同时要修改同一份数据,会出现什么状况?
    import time
    import threading
    def addNum():
    global num # 在每个线程中都获取这个全局变量
    print('--get num:', num)
    time.sleep(1)
    num -= 1 # 对此公共变量进行-1操作
    num = 100 # 设定一个共享变量
    thread_list = []
    for i in range(100):
    t = threading.Thread(target=addNum)
    t.start()
    thread_list.append(t)
    for t in thread_list: # 等待所有线程执行完毕
    t.join()
    print('final num:', num)
    正常来讲,这个num结果应该是0, 但在python 2.7上多运行几次,会发现,最后打印出来的num结果不总是0,为什么每次运行的结果不一样呢?
    哈,很简单,假设你有A,B两个线程,此时都 要对num 进行减1操作, 由于2个线程是并发同时运行的,所以2个线程很有可能同时拿走了num=100这个初始变量交给cpu去运算,
    当A线程去处完的结果是99,但此时B线程运算完的结果也是99,两个线程同时CPU运算的结果再赋值给num变量后,结果就都是99。那怎么办呢? 很简单,每个线程在要修改公共数据时,
    为了避免自己在还没改完的时候别人也来修改此数据,可以给这个数据加一把锁, 这样其它线程想修改此数据时就必须等待你修改完毕并把锁释放掉后才能再访问此数据。
    *注:不要在3.x上运行,不知为什么,3.x上的结果总是正确的,可能是自动加了锁
    import time
    import threading
    def addNum():
    global num # 在每个线程中都获取这个全局变量
    print('--get num:', num)
    time.sleep(1)
    lock.acquire() # 修改数据前加锁
    num -= 1 # 对此公共变量进行-1操作
    lock.release() # 修改后释放
    num = 100 # 设定一个共享变量
    thread_list = []
    lock = threading.Lock() # 生成全局锁
    for i in range(100):
    t = threading.Thread(target=addNum)
    t.start()
    thread_list.append(t)

    for t in thread_list: # 等待所有线程执行完毕
    t.join()

    print('final num:', num)
    
    
    #-----------------------加锁版本--------------------------
    import threading
    import time
    def addNUM():
    global num #在每个线程中都获取这个全局变量
    print('--get num:',num)
    time.sleep(1)
    lock.acquire() #修改数据前加锁
    num -=1 #对此公共变量进行-1操作
    lock.release()#修改后释放
    num = 100
    thread_list = []
    lock = threading.Lock() #生成全局锁
    for i in range(100):
    t = threading.Thread(target=addNum)
    t.start()
    thread_list.append(t)

    for t in thread_list:
    t.join()
    print('num:',num)
    那你又问了, 既然用户程序已经自己有锁了,那为什么C python还需要GIL呢?加入GIL主要的原因是为了降低程序的开发的复杂度,
    比如现在的你写python不需要关心内存回收的问题,因为Python解释器帮你自动定期进行内存回收,你可以理解为python解释器里有一个独立的线程,
    每过一段时间它起wake up做一次全局轮询看看哪些内存数据是可以被清空的,此时你自己的程序 里的线程和 py解释器自己的线程是并发运行的,
    假设你的线程删除了一个变量,py解释器的垃圾回收线程在清空这个变量的过程中的clearing时刻,可能一个其它线程正好又重新给这个还没来及得清空的内存空间赋值了,
    结果就有可能新赋值的数据被删除了,为了解决类似的问题,python解释器简单粗暴的加了锁,即当一个线程运行时,其它人都不能动,这样就解决了上述的问题,  这可以说是Python早期版本的遗留问题。
  • 相关阅读:
    Token ,Cookie和Session的区别
    极致Web性能 —— SPA性能指南
    关于前端数据&逻辑的思考
    移动端Retina屏boder 1px显示为2px或3px的解决方法
    Java连载8-基本数据类型2
    HTML连载25-通配符选择器&选择器综合练习
    Python连载25-函数tell&write&writeline$&持久化
    Python连载24-函数list&read&seek
    Java连载7-变量&数据类型
    HTML连载24-属性选择器(下)
  • 原文地址:https://www.cnblogs.com/hally/p/8331446.html
Copyright © 2011-2022 走看看