python 线程,进程,协程

python 线程,进程,协程,第1张

概述一、线程进程1、什么是进程(process)?(进程是资源集合)1.程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,这种执行的程序就称之为进程2.程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执 一、线程与进程

  1、什么是进程(process)?(进程是资源集合)

      1. 程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,这种执行的程序就称之为进程

      2. 程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念

      3. 在多道编程中,我们允许多个程序同时加载到内存中,在 *** 作系统的调度下,可以实现并发地执行。

      4. 进程的出现让每个用户感觉到自己独享cpu,因此,进程就是为了在cpu上实现多道编程而提出的。

      5. 进程之间有自己独立的内存,各进程之间不能相互访问

      6. 创建一个新线程很简单,创建新进程需要对父进程进行复制

      多道编程: 在计算机内存中同时存放几道相互独立的程序,他们共享系统资源

      单道编程: 计算机内存中只允许一个的程序运行

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

    1. 进程优点:

                          提供了多道编程,让我们感觉我们每个人都拥有自己的cpu和其他资源,可以提高计算机的利用率

    2. 进程的两个重要缺点:

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

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

                          c. 例如,我们在使用qq聊天, qq做为一个独立进程如果同一时间只能干一件事,那他如何实现在同一时刻 即能监听键盘输入、又能监听其它人给你发的消息

                          d. 你会说, *** 作系统不是有分时么?分时是指在不同进程间的分时呀

                          e. 即 *** 作系统处理一会你的qq任务,又切换到word文档任务上了,每个cpu时间片分给你的qq程序时,你的qq还是只能同时干一件事呀

  3、什么是线程(thread)(线程是 *** 作系统最小的调度单位)

      1. 线程是 *** 作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位

      2. 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务

      3. 无论你启多少个线程,你有多少个cpu, Python在执行的时候会淡定的在同一时刻只允许一个线程运行

      4. 进程本身是无法自己执行的,要 *** 作cpu,必须创建一个线程,线程是一系列指令的集合

      5. 所有在同一个进程里的线程是共享同一块内存空间的,不同进程间内存空间不同

      6. 同一个进程中的各线程可以相互访问资源,线程可以 *** 作同进程中的其他线程,但进程仅能 *** 作子进程

      7. 两个进程想通信,必须要通过一个中间代理

      8. 对主线程的修改可能回影响其他子线程,对主进程修改不会影响其他进程因为进程间内存相互独立,但是

          同一进程下的线程共享内存

  4、进程和线程的区别

      启动一个线程比启动一个进程快,运行速度没有可比性。

      先有一个进程然后才能有线程。

      1、进程包含线程

      2、线程共享内存空间

      3、进程内存是独立的(不可互相访问)

      4、进程可以生成子进程,子进程之间互相不能互相访问(相当于在父级进程克隆两个子进程)

      5、在一个进程里面线程之间可以交流。两个进程想通信,必须通过一个中间代理来实现

      6、创建新线程很简单,创建新进程需要对其父进程进行克隆。

      7、一个线程可以控制或 *** 作同一个进程里面的其它线程。但进程只能 *** 作子进程。

      8、父进程可以修改不影响子进程,但不能修改。

      9、线程可以帮助应用程序同时做几件事

  5、进程和程序的区别

      1. 程序只是一个普通文件,是一个机器代码指令和数据的集合,所以,程序是一个静态的实体

      2. 而进程是程序运行在数据集上的动态过程,进程是一个动态实体,它应创建而产生,应调度执行因等待资

          源或事件而被处于等待状态,因完成任务而被撤消

      3. 进程是系统进行资源分配和调度的一个独立单位

      4.一个程序对应多个进程,一个进程为多个程序服务(两者之间是多对多的关系)

      5. 一个程序执行在不同的数据集上就成为不同的进程,可以用进程控制块来唯一地标识每个进程

 

二、多线程

  Python多线程编程中常用方法:

      1、join()方法:如果一个线程或者在函数执行的过程中调用另一个线程,并且希望待其完成 *** 作后才能执行,
          那么在调用线程的时就可以使用被调线程的join方法join([timeout]) timeout:可选参数,线程运行的最长时间

      2、isAlive()方法:查看线程是否还在运行
      3、getname()方法:获得线程名
      4、setDaemon()方法:主线程退出时,需要子线程随主线程退出,则设置子线程的setDaemon()

  

   1、线程2种调用方式:直接调用(函数调用), 继承式调用(类调用)

import threadingimport timedef sayhi(num):                                   # 定义每个线程要运行的函数    print("running on number:%s" % num)    time.sleep(3)#1、target=sayhi :sayhi是定义的一个函数的名字#2、args=(1,)    : 括号内写的是函数的参数t1 = threading.Thread(target=sayhi, args=(1,))    # 生成一个线程实例t2 = threading.Thread(target=sayhi, args=(2,))    # 生成另一个线程实例t1.start()                                        # 启动线程t2.start()                                        # 启动另一个线程print(t1.getname())                               # 获取线程名print(t2.getname())
直接调用

class HMTHread(threading.Thread):    # 如果想给自定义线程类中传递参数 需要重写init方法    def __init__(self, name):        # 子类必须调用父类的同名init方法        threading.Thread.__init__(self)        self.name = name    # 任务1    def work1(self):        print(threading.current_thread())        print("工作1...")    # 任务2    def work2(self):        print(self.name)        print(threading.current_thread())        print("工作2...")    # 程序员需要实现一个方法run方法    def run(self):        print(threading.current_thread())        self.work2()        self.work1()if __name__ == '__main__':    # 如果是单线程(无论是主线程还是子线程 同一时间只能执行一个任务)    # 创建一个子线程    sub_thread = HMTHread("自定义线程")    # 启动线程(无论使用系统线程 还是自定义线程 都需要启动线程)    sub_thread.start()
继承式调用

  2、for循环同时启动多个线程

      (PS:下面利用for循环同时启动50个线程并行执行,执行时间是3秒而不是所有线程执行时间的总和)

import threadingimport timedef sayhi(num): #定义每个线程要运行的函数    print("running on number:%s" %num)    time.sleep(3)for i in range(50):    t = threading.Thread(target=sayhi,args=('t-%s'%i,))    t.start()
开启多个线程

  3、t.join(): 实现所有线程都执行结束后再执行主线程

      说明:在4中虽然可以实现50个线程同时并发执行,但是主线程不会等待子线程结束在这里我们可以使用t.join()指定等待某个线程结束的结果

import threadingimport timestart_time = time.time()def sayhi(num): #定义每个线程要运行的函数    print("running on number:%s" %num)    time.sleep(3)t_obJs = []    #将进程实例对象存储在这个列表中for i in range(50):    t = threading.Thread(target=sayhi,args=('t-%s'%i,))    t.start()          #启动一个线程,程序不会阻塞    t_obJs.append(t)print(threading.active_count())    #打印当前活跃进程数量for t in t_obJs: #利用for循环等待上面50个进程全部结束    t.join()     #阻塞某个程序print(threading.current_thread())    #打印执行这个命令进程print("----------------all threads has finished.....")print(threading.active_count())print('cost time:',time.time() - start_time)
t.join() 主线程等待子线程

  4、setDaemon(): 守护线程,主线程退出时,需要子线程随主线程退出

import threadingimport timestart_time = time.time()def sayhi(num): #定义每个线程要运行的函数    print("running on number:%s" %num)    time.sleep(3)for i in range(50):    t = threading.Thread(target=sayhi,args=('t-%s'%i,))    t.setDaemon(True)  #把当前线程变成守护线程,必须在t.start()前设置    t.start()          #启动一个线程,程序不会阻塞print('cost time:',time.time() - start_time)
守护线程

   注:因为刚刚创建的线程是守护线程,所以主线程结束后子线程就结束了,运行时间不是3秒而是0.01秒

  5、GIL锁和用户锁(Global Interpreter Lock 全局解释器锁

    1.全局解释器锁:保证同一时间仅有一个线程对资源有 *** 作权限

      作用:在一个进程内,同一时刻只能有一个线程通过GIL锁 被CUP调用,切换条件:I/O *** 作、固定时间(系统决定)

      说明:python多线程中GIL锁只是在cpu *** 作时(如:计算)才是串行的,其他都是并行的,所以比串行快很多

      1)为了解决不同线程同时访问同一资源时,数据保护问题,而产生了GIL

      2)GIL在解释器的层面限制了程序在同一时间只有一个线程被cpu实际执行,而不管你的程序里实际开了多少条线程

      3)为了解决这个问题,cpython自己定义了一个全局解释器锁,同一时间仅仅有一个线程可以拿到这个数据

      4)python之所以会产生这种不好的状况是因为python启用一个线程是调用 *** 作系统原生线程,就是C接口

      5)但是这仅仅是cpython这个版本的问题,在PyPy,中就没有这种缺陷

    2. 用户锁:线程锁(互斥锁Mutex)  :当前线程还未 *** 作完成前其他所有线程都无法对其 *** 作,即使已经释放了GIL锁

      1. 在有GIL锁时为何还需要用户锁

          1)GIL锁只能保证同一时间只能有一个线程对某个资源 *** 作,但当上一个线程还未执行完毕时可能就会释放GIL,其他线程就可以 *** 作了

      2. 线程锁的原理

          1)当一个线程对某个资源进行cpu计算的 *** 作时加一个线程锁,只有当前线程计算完成主动释放锁,其他线程才能对其 *** 作

          2)这样就可以防止还未计算完成,释放GIL锁后其他线程对这个资源 *** 作导致混乱问题

import threading# 定义一个全局变量num = 0# 定义一个变量 保存互斥锁对象lock = threading.Lock()def work1():    global num    for i in range(1000000):        # 上锁        lock.acquire()        num += 1        # 解锁        lock.release()    print("work1:", num)def work2():    global num    for i in range(1000000):        # 上锁        lock.acquire()        num += 1        # 解锁        lock.release()    print("work2:", num)if __name__ == '__main__':    # 创建两个子线程    work1_thread = threading.Thread(target=work1)    work2_thread = threading.Thread(target=work2)    # 启动线程    work1_thread.start()    work2_thread.start()
线程锁

        1、使用线程锁解决上面问题的原理

          1) 在GIL锁中再加一个线程锁,线程锁是用户层面的锁

          2) 线程锁就是一个线程在对数据 *** 作前加一把锁,防止其他线程复制或者 *** 作这个数据

          3) 只有这个线程对数据 *** 作完毕后才会释放这个锁,其他线程才能 *** 作这个数据

        2、定义一个线程锁非常简单只用三步:

          第一步:  lock = threading.Lock()                        #定义一把锁

          第二步:  lock.acquire()                                        #对数据 *** 作前加锁防止数据被另一线程 *** 作

          第三步:  lock.release()                                         #对数据 *** 作完成后释放锁

 

  6、死锁

    1. 死锁定义

        两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。

    2. 死锁举例

      1. 启动5个线程,执行run方法,假如thread1首先抢到了A锁,此时thread1没有释放A锁,紧接着执行代码mutexB.acquire(),抢到了B锁,
          在抢B锁时候,没有其他线程与thread1争抢,因为A锁没有释放,其他线程只能等待
      2. thread1执行完func1函数,然后执行func2函数,此时thread1拿到B锁,然后执行time.sleep(2),此时不会释放B锁
      3. 在thread1执行func2的同时thread2开始执行func1获取到了A锁,然后继续要获取B锁
      4. 不幸的是B锁还被thread1占用,thread1占用B锁时还需要同时获取A锁才能向下执行,但是此时发现A锁已经被thread2暂用,这样就死锁了

from threading import Thread,Lockimport timemutexA=Lock()mutexB=Lock()class MyThread(Thread):    def run(self):        self.func1()        self.func2()    def func1(self):        mutexA.acquire()        print('3[41m%s 拿到A锁3[0m' %self.name)        mutexB.acquire()        print('3[42m%s 拿到B锁3[0m' %self.name)        mutexB.release()        mutexA.release()    def func2(self):        mutexB.acquire()        print('3[43m%s 拿到B锁3[0m' %self.name)        time.sleep(2)        mutexA.acquire()        print('3[44m%s 拿到A锁3[0m' %self.name)        mutexA.release()        mutexB.release()if __name__ == '__main__':    for i in range(2):        t=MyThread()        t.start()# 运行结果:输出下面结果后程序卡死,不再向下进行了# Thread-1 拿到A锁# Thread-1 拿到B锁# Thread-1 拿到B锁# Thread-2 拿到A锁
死锁

 

三、进程  1、进程间通信方式

    1.利用Queues实现父进程到子进程(或子进程间)的数据传递  

    2.使用管道pipe实现两个进程间数据传递  

    3.Managers实现很多进程间数据共享

from multiprocessing import Process, Queuedef f(qq):  # 将符进程中的q传递过来叫qq    qq.put([42, None, 'hello'])  # 此时子进程就可以使用符进程中的qif __name__ == '__main__':    q = Queue()  # 使用Queue()在父进程中定义一个队列实例q    p = Process(target=f, args=(q,))  # 在父进程中起一个子进程 p,将父进程刚定义的q传递给子进程p    p.start()    print(q.get())    p.join()# 运行结果: [42, None, 'hello']
Queue

from multiprocessing import Process, Pipedef f(conn):    conn.send([42, None, 'hello'])  # 3 子进程发送数据,就像socket一样    print("son process recv:", conn.recv())    conn.close()if __name__ == '__main__':    parent_conn, child_conn = Pipe()    # 1 生成一个管道实例,实例一生成就会生成两个返回对象,一个是管道这头,一个是管道那头    p = Process(target=f, args=(child_conn,))  # 2 启动一个子进程将管道其中一头传递给子进程    p.start()    print(parent_conn.recv())  # 4 父进程收消息 # prints "[42, None, 'hello']"    parent_conn.send('i am parent process')    p.join()# 运行结果:# [42, None, 'hello']# son process recv: i am parent process
pipe

from multiprocessing import Process, Managerimport osdef f(d, l):    d[1] = '1'  # 是个进程对字典放入的是同一个值,所以看上去效果不明显    l.append(os.getpID())  # 将这是个进程的进程ID放入列表中if __name__ == '__main__':    with Manager() as manager:  # 1 将Manager()赋值给manager        d = manager.dict()  # 2 定义一个可以在多个进程间可以共享的字典        l = manager.List(range(5))  # 3 定义一个可以在多个进程间可以共享的列表,默认写五个数据        p_List = []        for i in range(10):  # 生成是个进程            p = Process(target=f, args=(d, l))  # 将刚刚生成的可共享字典和列表传递给子进程            p.start()            p_List.append(p)        for res in p_List:            res.join()        print(d)        print(l)
message  2、进程池

    1. 进程池的作用就是限制同一时间可以启动进程的=数量
    2. 进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进乘 ,那么程序就会等待,直到进程池中有可用进程为止。
    3. 进程池中有两个方法:
       1)apply: 多个进程异步执行,一个一个的执行
             2)apply_async: 多个进程同步执行,同时执行多个进程

from  multiprocessing import Process,Poolimport time,osdef foo(i):    time.sleep(2)    print("in the process",os.getpID()) #打印子进程的pID    return i+100def call(arg):    print('-->exec done:',arg,os.getpID())if __name__ == '__main__':    pool = Pool(3)                      #进程池最多允许5个进程放入进程池    print("主进程pID:",os.getpID())     #打印父进程的pID    for i in range(10):        #用法1 callback作用是指定只有当Foo运行结束后就执行callback调用的函数,父进程调用的callback函数        pool.apply_async(func=foo, args=(i,),callback=call)        #用法2 串行 启动进程不在用Process而是直接用pool.apply()        # pool.apply(func=foo, args=(i,))    print('end')    pool.close()    #关闭pool    pool.join()     #进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭。
进程池  3、僵尸进程

    僵尸进程定义

       1. 僵尸进程产生的原因就是父进程产生子进程后,子进程先于父进程退出

       2. 但是父进程由于种种原因,并没有处理子进程发送的退出信号,那么这个子进程就会成为僵尸进程。

 

@H_106_403@

#!/usr/bin/env python#Coding=utf8 import os, sys, time#产生子进程pID = os.fork() if pID == 0:    #子进程退出    sys.exit(0)#父进程休息30秒time.sleep(30)# 先产生一个子进程,子进程退出,父进程休息30秒,那就会产生一个僵尸进程
defunct.py
###查看僵尸进程结果
[root@linux-node4 ~]# ps -ef| grep defunctroot 110401 96083 0 19:11 pts/2 00:00:00 python defunct.pyroot 110402 110401 0 19:11 pts/2 00:00:00 [python] <defunct>root 110406 96105 0 19:11 pts/3 00:00:00 grep --color=auto defunct

  

四、协程  1.1 什么是协程

    1)协程微线程,纤程,本质是一个单线程

    2)协程能在单线程处理高并发,因为遇到IO自动切换

@H_403_430@线程遇到I/O *** 作会等待、阻塞协程遇到I/O会自动切换(剩下的只有cpu *** 作)@H_403_430@线程的状态保存在cpu的寄存器和栈里而协程拥有自己的空间,所以无需上下文切换的开销,所以快   3)

为甚么协程能够遇到I/O自动切换

@H_403_430@greenlet是C语言写的一个模块,遇到IO手动切换@H_403_430@协程有一个gevent模块(封装了greenlet模块),遇到I/O自动切换

     4)协程拥有自己的空间,所以无需上下文切换的开销

  1.2 协程优缺点@H_403_430@

协程缺点

@H_403_430@无法利用多核资源:协程的本质是个单线程,它不能同时将 单个cpu 的多个核用上,协程需要和进程配合才能运行在多cpu上@H_403_430@协程如果阻塞掉,整个程序都阻塞@H_403_430@

协程最大的优点

@H_403_430@不仅是处理高并发(单线程下处理高并发)@H_403_430@特别节省资源(协程本质是一个单线程,当然节省资源)@H_403_430@500W日活,用PHP写需要两百多态机器,但是golang只需要二十多太机器  1.3 Python中协程的模块@H_403_430@greenlet:遇到I/O手动切换,是一个C模块@H_403_430@gevent:对greenlet封装,遇到I/O自动切换借助C语言库greenlet)@H_403_430@asyncio:和gevent一样,也是实现协程的一个模块(python自己实现

from urllib import requestimport gevent,timefrom gevent import monkeymonkey.patch_all()      #把当前程序所有的I/O *** 作给我单独做上标记def f(url):    print('GET: %s' % url)    resp = request.urlopen(url)    data = resp.read()    print('%d bytes received from %s.' % (len(data), url))#1 并发执行部分time_binxing = time.time()gevent.joinall([        gevent.spawn(f, 'https://www.python.org/'),        gevent.spawn(f, 'https://www.yahoo.com/'),        gevent.spawn(f, 'https://github.com/'),])print("并行时间:",time.time()-time_binxing)#2 串行部分time_chuanxing = time.time()urls = [        'https://www.python.org/',        'https://www.yahoo.com/',        'https://github.com/',                                        ]for url in urls:    f(url)print("串行时间:",time.time()-time_chuanxing)# 注:为什么要在文件开通使用monkey.patch_all()# 1. 因为有很多模块在使用I / O *** 作时Gevent是无法捕获的,所以为了使Gevent能够识别出程序中的I / O *** 作。# 2. 就必须使用Gevent模块的monkey模块,把当前程序所有的I / O *** 作给我单独做上标记# 3.使用monkey做标记仅用两步即可:      第一步(导入monkey模块):  from gevent import monkey      第二步(声明做标记)    :   monkey.patch_all()
gevent

 

   

 

总结

以上是内存溢出为你收集整理的python 线程,进程,协程全部内容,希望文章能够帮你解决python 线程,进程,协程所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/1188601.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-03
下一篇 2022-06-03

发表评论

登录后才能评论

评论列表(0条)

保存