进程与线程

 进程
        计算机程序只是存储在磁盘上的可执行二进制(或其他类型)文件。只有把它们加载到
        内存中并被操作系统调用,才拥有其生命期。进程(有时称为重量级进程)则是一个执行中
        的程序。每个进程都拥有自己的地址空间、内存、数据栈以及其他用于跟踪执行的辅助数据。
    线程
        线程(有时候称为轻量级进程)与进程类似,不过它们是在同一个进程下执行的,并
        共享相同的上下文。可以将它们认为是在一个主进程或“主线程”中并行运行的一些“迷
        你进程”。
    让步
        线程包括开始、执行顺序和结束三部分。它有一个指令指针,用于记录当前运行的上下
        文。当其他线程运行时,它可以被抢占(中断)和临时挂起(也称为睡眠)——这种做法叫
        做让步(yielding)。
    单核和多核是如何实现并发的
        一个进程中的各个线程与主线程共享同一片数据空间,因此相比于独立的进程而言,线
        程间的信息共享和通信更加容易。线程一般是以并发方式执行的,正是由于这种并行和数据
        共享机制,使得多任务间的协作成为可能。当然,在单核 CPU 系统中,因为真正的并发是不
        可能的,所以线程的执行实际上是这样规划的:每个线程运行一小会儿,然后让步给其他线
        程(再次排队等待更多的 CPU 时间)。在整个进程的执行过程中,每个线程执行它自己特定
        的任务,在必要时和其他线程进行结果通信。
        当然,这种共享并不是没有风险的。如果两个或多个线程访问同一片数据,由于数据访
        问顺序不同,可能导致结果不一致。这种情况通常称为竞态条件(race condition)。幸运的是,
        大多数线程库都有一些同步原语,以允许线程管理器控制执行和访问。
    守护线程: setDaemon()
        import threading
        import time

        def music():
            print("start music %s" % time.ctime())
            time.sleep(3)
            print("stop music %s" % time.ctime())

        def game():
            print("start game %s" % time.ctime())
            time.sleep(5)
            print("stop game %s" % time.ctime())     # 13

        if __name__ == "__main__":
            t1 = threading.Thread(target=music)
            t2 = threading.Thread(target=game)

            t1.start()
            t2.setDaemon(True)   # 将t1设置为守护线程
            t2.start()

            # t2.setDaemon(True)   # 将t1设置为守护线程
            """
            守护线程:没有守护线程时,主线程语句执行完后,等待子线程,子线程全部结束后,主线程结束,
            有守护线程时,主线程语句执行完后,不等待守护线程,非守护线程结束后,主线程立即结束,
            守护线程不论是否结束,都跟随主线程一起结束
            """
            print("ending.........")
            """
            t2设置为守护线程后,则t1线程结束后,主线程结束,守护线程t2立即结束,13行未被执行
            start music Tue Aug 27 08:55:21 2019
            start game Tue Aug 27 08:55:21 2019
            ending.........
            stop music Tue Aug 27 08:55:24 2019
            """
    join()方法,会等待线程调用了该方法的线程结束后,在向下继续执行
        import threading
        import time

        def music():
            print("start music %s" % time.ctime())   #6
            time.sleep(3)
            print("stop music %s" % time.ctime())    #8

        def game():
            print("start game %s" % time.ctime())    #11
            time.sleep(5)
            print("stop game %s" % time.ctime())     #13

        if __name__  == "__main__":
            # t1 = threading.Thread(target=music)  # 如果需要传参数 ,args=参数
            # t2 = threading.Thread(target=game)
            # t1.start()
            # t2.start()
            # print("ending>>>>>>>")    #20  # 6,11,20同时执行,3秒后8执行,再过2秒13执行

            #jion()方法由实例调用,作用是等待实例执行完后继续执行后面的语句
            t1 = threading.Thread(target=music)  # 如果需要传参数 ,args=参数
            t2 = threading.Thread(target=game)
            t1.start()
            t2.start()
            t2.join()
            """
            join()方法,调用此方法的线程必须结束后才会继续向下执行,
            也就是等待线程结束后才会继续执行
            同时执行6,11,3秒后t1结束,5秒后t2结束,执行34
            """

            print("ending>>>>>>>")# 34   # 6,11同时执行,3秒后8执行,再过2秒13,34同时执行
    实例的方法
        join():等待线程
        setDaemon():守护线程
        run():线程被cpu调用后自动执行
        start():启动线程活动,使处于等待被cpu调用的状态
        isAlive():返回线程是否活动
        getName():返回线程名,默认:thread-1
        setName():设置线程名

    threading提供的一些方法
        #threading.currentThread():返回当前的线程变量
        #threading.enumerate():返回正在运行的线程的列表
        #threading.activeCount():返回正在运行的线程数量
    GIL:
        python的GIL    全局解释器锁,这是在解释器中引入的,简单来讲,就是在一个进程中不允许CPU同时执行多个
        线程,哪怕是多核CPU,也只能采用让步的方式轮循    
        线程执行的任务有两种:1 IO密集型
                             2  计算密集型
        对于计算密集型,因为让步轮循的关系,反而不如串行执行的效率高
        但是IO密集型不同,等待IO时其他线程继续执行,节省了等待IO的时间,此时多线程是有意义的
        GIL的测试
            import threading
            import time

            def add():
                sum = 0
                for i in range(10000000):
                    sum += i
                print("sum",sum)
            def mul():
                sum1 = 1
                for i in range(1,100000):
                    sum1 *= i
                print("sum1",sum1)

            if __name__ =="__main__":
                start = time.time()

                t1 = threading.Thread(target=add)
                t2 = threading.Thread(target=mul)

                l = []
                l.append(t1)
                l.append(t2)

                # for t in l:
                #     t.start()
                #
                # for t in l:
                #     t.join()    #   cost time: 8.800450
                add()
                mul()   #cost time: 7.661081

                print("cost time: %f" % (time.time()-start))
            """
            会发现串行执行会比线程执行更快,这就是因为GIL的影响
            """            
    同步锁:        
        假设我们要做一个100的累减,通过使用100个线程来实现
            import threading
            import time

            # def cut():
            #     global num
            #     num-=1            #    out:   0
            # def cut():
            #     global num
            #     temp = num
            #     time.sleep(0.001)
            #     num = temp-1          #   time.sleep(0.01):  out:  95 96
                                      #   time.sleep(0.001):  out :    78 82  81
            """
            会出现这种现象就是因为每个线程在自己的轮循时间内有阻塞,会立刻切换其他线程,但其他线程拿到的num
            并没有被前一个线程改变,阻塞时间足够大的情况下,所有的线程拿到的num都是100,说以最后的结果num = 99
            而线程在自己轮循时间内完成了对num的改变,之后的线程拿到的num就都是改变后的了
            """
            # num = 100
            # l = []
            # for i in range(100):
            #     t = threading.Thread(target=cut)
            #     t.start()
            #     l.append(t)
            #
            #
            # for j in l:
            #     j.join()
            #
            # print(num)    # 0

            # 若果想要在线程有阻塞或者,轮循时间内无法完成任务的情况下,实现目的,就要使用同步锁
            #同步锁:同步锁之内的语句在未执行完的情况下不允许切换线程

            def cut():
                global num
                lock.acquire()    #使用同步锁包围要同步的执行语句  这是类LockType() 中的方法
                temp = num
                time.sleep(0.001)
                num = temp-1
                lock.release()    #使用同步锁包围要同步的执行语句
            num = 100
            l = []

            lock = threading.Lock()  # 创建一个实例LockType()
            for i in range(100):
                t = threading.Thread(target=cut)
                t.start()
                l.append(t)


            for j in l:
                j.join()

            print(num)    # 0    
    同步对象
    可以实现控制线程的执行顺序
    event = threadin.Event
    event.wait()      未设置标志位时阻塞,标志位被设置时等同于pass
    event.set()        设置标志位
    event.clear()    清除标志位
    信号量
    semaphore=threading.Semaphore(val) # val决定同时启动的线程数量    
    # 信号量就相当于停车位,停车位满了,后来的车就必须等待,直到车位被释放
    举例:
        import threading
        import time
        class Mythread(threading.Thread):
            def run(self):
                if semaphore.acquire():                     #semaphore.acquire() == True
                    print(self.getName())
                    time.sleep(3)
                semaphore.release()

        if __name__ == "__main__":
            L = []
            semaphore = threading.Semaphore(5)
            for i in range(100):
                L.append(Mythread())
            for j in L:
                j.start()                           # 效果是每隔3秒执行五个线程        
    队列
        import queue

        # 队列的三种模式
        # 模式一   先进先出
        q = queue.Queue()   # ,还可以传入参数val=5,表示只允许put进5个元素
        """
        [1, 2, 3]
        1
        wa
        (1, 2)
        {'name': 'alxe'}
        """
        q.put([1,2,3])
        q.put(1)
        q.put("wa")
        q.put((1,2))
        q.put({"name":"alxe"})
        while True:
            date = q.get()
            print(date)

        #模式二  先进后出
        q = queue.LifoQueue() 
        """
        {'name': 'alxe'}
        (1, 2)
        wa
        1
        [1, 2, 3]
        """
        q.put([1,2,3])
        q.put(1)
        q.put("wa")
        q.put((1,2))
        q.put({"name":"alxe"})
        while True:
            date = q.get()
            print(date)

        #模式三   按照优先级
        q = queue.Queue()   # 按照优先级
        """
        [1, 1]
        [2, (1, 2)]
        [3, 'wa']
        [4, {'name': 'alxe'}]
        [5, [1, 2, 3]]
        """
        q.put([5,[1,2,3]])
        q.put([1,1])
        q.put([3,"wa"])
        q.put([2,(1,2)])
        q.put([4,{"name":"alxe"}])                 # block = False:当队列为满时,继续传入会报错
        while True:
            date = q.get(block=False)                          # block = False :当队列为空时报错
            """
                raise Empty
                _queue.Empty           # 报错,队列已满
            """
            print(date)

        q = queue.Queue(4)   # 按照优先级
        """
        [1, 1]
        [2, (1, 2)]
        [3, 'wa']
        [4, {'name': 'alxe'}]
        [5, [1, 2, 3]]
        """
        q.put([5,[1,2,3]])
        q.put([1,1])
        q.put([3,"wa"])
        q.put([2,(1,2)])
        # 队列满了之后,在继续装入数据,会一直等待,直到队列中数据被取出,有空位为止
        # q.put([4,{"name":"alxe"}])  # block = False:当队列为满时,继续传入会报错
        """
            raise Full
        queue.Full                     # 报错,队列已满
        """
        while True:
            # 取出全部数据后等待,直到再次有数据被装入
            date = q.get()            # block = False :当队列为空时报错
            """
                raise Empty
                _queue.Empty           # 报错,队列已空
            """
            print(date)

        #其他方法
        q = queue.Queue(2)

        q.put([5,[1,2,3]])
        q.put([1,1])              # q.put_nowait()  相当于 q.put("wan",block=False)
        print(q.empty())   #队列是否为空
        print(q.full())    #队列是否为满
        print(q.qsize())   #队列中的数据个数
        """
        False
        True
        2
        """

        q.task_done()   # 在完成一项工作后,向完成任务的队列发送一个信号,由q.join()接收
        q.join()        # 实际上是等到列表为空之后在进行其他操作
        while True:
            date = q.get()   # q.get_nowait()  相当于  q.get(block=False)
            print(date)
    多进程调用:
        import multiprocessing
        import time
        import os
        # 调用方式一
        def foo(num):
            # print(num)
            print("%s......%s" % (num,time.ctime()))

        if __name__ == "__main__":
            L = []
            for i in range(3):
                p = multiprocessing.Process(target=foo,args=("1",))
                L.append(p)
                p.start()
            for j in L:
                j.join()
            print("ending........")
        # 调用方式二
        class Myprocess(multiprocessing.Process):
            def __init__(self):
                super(Myprocess,self).__init__()
            def run(self):
                print("%s......%s" % (self.name,time.ctime()))
        if __name__ == "__main__":
            L = []
            for i in range(3):
                p = Myprocess()
                L.append(p)
                p.start()
            for j in L:
                j.join()
            print("ending........")

        # 进程关系
        class Myprocess(multiprocessing.Process):
            def __init__(self):
                super(Myprocess,self).__init__()
            def run(self):
                print("PPID  :%s" % (os.getppid()))   # 获取进程的父ID
                print("PID   :%s" % os.getpid())
        if __name__ == "__main__":
            L = []
            print("PPID  :%s" % (os.getppid()))
            print("PID   :%s" % os.getpid())
            # for i in range(3):
            p = Myprocess()
            p2 = Myprocess()
                # L.append(p)
            p.daemon = True           # 守护进程
            p.start()
            p2.start()
            # for j in L:
            #     j.join()
            p.join()
            p2.join()
            print("ending........")
    进程间的通信:
        # 进程间通信的三种方式
        import multiprocessing
        import queue
        import time

        # 一  通过进程队列(注意它与线程队列的区别),将进程队列当作参数传给子进程

        def foo(q):
            # q.put("alxe")
            date = q.get()  # 获取进程队列中数据
            # time.sleep(1)
            date["name"] = "alxe" # 对数据进行修改
            # print(date)

        if __name__ == "__main__":
            q = multiprocessing.Queue()  # 创建一个进程队列
            d = {"name": "lhf"}
            q.put(d)
            print("================")
            p = multiprocessing.Process(target=foo,args=(q,))
            p.start()   # 启动子线程对数据进行修改
            # p.daemon(True)
            p.join() # 等待子进程完成修改
            print(q.get())            # 在主进程中获取队列数据,看是否被修改
            # print(q.get())
        """
        特别注意:同一进程队列在子进程中被get的数据,在主进程中无法再被获取
        """
        # 二  管道 farther.conn,sub.conn = Pipe() 像socket中的通信

        def foo(sub_conn):
            date = sub_conn.recv()
            print(date)
            sub_conn.send("nihao")

        if __name__ == "__main__":
            main_conn,sub_conn = multiprocessing.Pipe()
            d = {"name": "alxe"}

            p = multiprocessing.Process(target=foo,args=(sub_conn,))
            p.start()   # 启动子线程对数据进行修改
            # p.daemon(True)
            main_conn.send(d)
            date = main_conn.recv()
            print(date)
        """
        管道只能用于进程间的数据发送与接收(数据交互)
        """
        # 三  数据共享 manager      with Manager() as manager:
        '''
        以上两种方法只实现了数据交互,没有真正实现数据共享
        support types list, dict, Namespace, Lock, RLock, Semaphore, BoundedSemaphore, 
        Condition, Event, Barrier, Queue, Value and Array.
        '''
        def foo(d,l):
            d["name"] = "alxe"
            l.append("wang")
            print(id(d),id(l))

        if __name__ == "__main__":
            with multiprocessing.Manager() as manger:
                d = manger.dict()
                l = manger.list(range(5))
                p = multiprocessing.Process(target=foo,args=(d,l,))
                p.start()
                p.join()
                print(d)
                print(l)
                print(id(d),id(l))
    协程:
           # 协程是面向用户的,在线程和进程中,我们无法控制每个线程或进程的执行顺序,它们都通过抢占
        # 决定执行顺序,协程就是为了解决这个问题

        # 协程是由yield来实现的,这是协程的首先回顾一下
        # def foo():
        #     print("ok1")
        #     s = yield 4    # 两步操作,1.执行到这一句return 4 2.s = f.send的值
        #     print(s)
        #     # print("ok2")
        # f = foo()
        # # 我们定义了一个生成器(注意不是函数了),要想执行,需要next方法
        # #方法一:
        # # f.__next__()
        # #方法二
        # # next(f)
        # #方法三
        # date = f.__next__()   # 可以发送一个参数,由对应的yield接收
        # print(date)
        # f.send(6)   # yield的赋值操作是在执行到yield之后的下一次next中

        # 下面再用yield的方法实现之前的生产者消费者模型
        # import time
        #
        # def consumer(n):
        #     print("顾客 %s 来到包子铺" % n)
        #     while True:
        #         m = yield
        #         print("\033[32;1m顾客%s 吃掉了%s\033[0m" % (n,m))
        # def producer(con1,con2):
        #     next(con1)
        #     next(con2)
        #     i = 0
        #     while True:
        #         time.sleep(1)
        #         print("producer 开始做包子 %s %s" % (i,i+1))
        #         con1.send(i)
        #         con2.send(i+1)
        #         i += 2
        #
        # if __name__ == "__main__":
        #     con1 = consumer(1)
        #     con2 = consumer(2)
        #     producer(con1,con2)

        # 用greenlet实现函数间的切换,不需先将函数声明为生成器
        # from greenlet import greenlet
        # def foo1():
        #     print(1)
        #     g2.switch()
        #     print(2)
        #
        # def foo2():
        #     print(3)
        #     g1.switch()
        #     print(4)
        #
        # g1 = greenlet(foo1)
        # g2 = greenlet(foo2)
        # g1.switch()

        # import asyncio
        # async def foo1():
        #     await asyncio.sleep(1)
        #     print("1")
        #
        # async def main():
        #     test1 = asyncio.create_task(foo1())
        #     test2 = asyncio.create_task(foo1())
        #     test3 = asyncio.create_task(foo1())
        #     test4 = asyncio.create_task(foo1())
        #     test5 = asyncio.create_task(foo1())
        #     test6 = asyncio.create_task(foo1())
        #
        #     await test1
        #     await test2
        #     await test3
        #     await test4
        #     await test5
        #     await test6         # 执行6次也是等待1秒
        # asyncio.run(main())

        # import asyncio
        # async def foo1(i):
        #     await asyncio.sleep(1)
        #     print(i)
        # loop = asyncio.get_event_loop()
        # myfun_list = (foo1(i) for i in range(100))
        # print(myfun_list)
        # loop.run_until_complete(asyncio.gather(*myfun_list))




        # import asyncio
        # async def foo1(name):
        #     await asyncio.sleep(1)
        #     print(name)
        # async def main(foo1):
        #     myfun_list = (foo1(i) for i in range(100))
        #     print(myfun_list)
        #     await asyncio.gather(*myfun_list)
        # asyncio.run(main(foo1))
        #
        # import asyncio
        # async def foo1(name):
        #     await asyncio.sleep(1)
        #     print(name)
        # async def main():
        #     await asyncio.gather(
        #     foo1("who"),
        #     foo1("is"),
        #     foo1("alxe"),
        #     )
        # asyncio.run(main())

        def f(i):
            print(i)
        (f(i) for i in range(1)).__next__()
        (f(i) for i in range(1)).__next__()
    进程池和线程池:
        from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
        import requests
        """
        多进程和多线程在代码上只是生成的pool不同,一个进程执行时,系统会为其分配必要的资源(地址空间、内存、数据栈以及其他用于跟踪执行的辅助数据),
        当一个进程下有多个线程时,因为GIL锁(全局解释器锁)的限制,线程在执行时只能依次(或让步)由同一CPU执行,
        此时如果线程执行的是计算任务,由于计算必须由CPU计算,所以线程只能依次执行,多线程变得没有意义
        这样计算密集的任务就应该由多进程执行,它不受GIL锁的限制
        IO密集型的任务就使用多线程,因为询问CPU几乎不耗时

        """

        pool = ThreadPoolExecutor(3)

        url_list = [
            "https://www.baidu.com",
            "https://www.zhihu.com",
            "https://www.bilibili.com/",
            "https://www.cnblogs.com/",
            "https://www.iconfont.cn/collections/index?spm=a313x.7781069.1998910419.7&type=3",
            "https://www.bootcss.com/",
            "http://jquery.cuishifeng.cn/"
        ]
        def visit_url(url):
            response = requests.request(
                method="GET",
                url=url,
                headers={'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36'}
            )
            print(url,response.status_code)
            return response
        def done_callback(response,*args,**kwargs):
            print(response)
        for url in url_list:
            v = pool.submit(visit_url,url)
            v.add_done_callback(done_callback)
        pool.shutdown(wait=True)

 

posted @ 2020-06-13 17:49  pywhy  阅读(99)  评论(0编辑  收藏  举报