zoukankan      html  css  js  c++  java
  • 线程,进程和协程

    线程  进程  协程

    进程:狭义定义:进程是正在运行的程序的实例(an instance of a computer program that is being executed)。

    简而言之,就像qq要以一个整体的形式暴露给操作系统管理,里面包含对各种资源的调用,内存的管理,网络接口的调用等,对各种资源管理的集合就可以称为 进程。

    进程要操作CPU必须要先创建一个线程,在当代面向线程设计的计算机结构中,进程是线程的容器。

    线程:是程序执行流的最小单元,是一串指令的集合

    区别:

    1,线程共享内存空间和资源(对于线程之间而言),而进程内存是独立的

    2,一个进程中的线程之间可以直接交流(读取全局变量),两个进程想通信,必须借助中间代理

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

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

    5,线程启动速度比进程快,线程的上下文切换要比进程快的多

    对于IO操作,一般使用多线程来提高并发。
    对于计算性操作,一般使用多进程来提高并发。
    归根结底是因为下面的计算机规定:IO操作不占用CPU,计算性操作占用CPU,另外对于一个原因就是GIL

    GIL

    GIL(全称Global Interpreter Lock)全局解释器锁(java和C#中没有这一机制)
    隐含的意思:对于任何Python程序,不管有多少的CPU和线程,任何时候都总是只有一个线程在执行。

    在Python多线程下,每个线程的执行方式:
    1、获取GIL
    2、执行代码直到sleep或者是python虚拟机将其挂起。
    3、释放GIL

    可见,某个线程想要执行,必须先拿到GIL,我们可以把GIL看作是“通行证”,并且在一个python进程中,GIL只有一个。拿不到通行证的线程,就不允许进入CPU执行。每次释放GIL锁,线程进行锁竞争、切换线程,会消耗资源。并且由于GIL锁存在,python里一个进程永远只能同时执行一个线程(拿到GIL的线程才能执行),这就是为什么在多核CPU上,python的多线程效率并不高。

    请注意:多核多线程比单核多线程更差,原因是单核下多线程,每次释放GIL,唤醒的那个线程都能获取到GIL锁,所以能够无缝执行,但多核下,CPU0释放GIL后,其他CPU上的线程都会进行竞争,但GIL可能会马上又被CPU0拿到,导致其他几个CPU上被唤醒后的线程会醒着等待到切换时间后又进入待调度状态,这样会造成线程颠簸(thrashing),导致效率更低。python下想要充分利用多核CPU,就用多进程。

    总结:多线程和多进程的目的在于提高并发,IO密集型用多线程,计算密集型用多进程。

    一,线程

      1,线程的基本操作

    import threading
    import time
    
    
    def f1(*args):
    	time.sleep(5)
    	print(args)
    
    # 单进程,单线程
    t = threading.Thread(target=f1, args=(123,))
    t.setDaemon(True)
    # 设置为True,表示主线程不等子线程执行完就结束整个程序
    # 设置为False,表示主进程等子线程结束后才会结束整个程序
    t.start()   # 一个线程
    
    t.join()
    # 不加参数,默认表示主线程一直等待,等到子线程执行完毕,才会往下走。
    # 加参数,参数表示主线程在此最多等待n秒
    
    print("end")
    

      

    注意:t.join() 和 t.setDaemon(False)意义不同,t.join()会停住等到子线程执行完才往下走。t.setDaemon(False)不会停止会先执行下面的程序,然后再等待子线程执行完才结束。

      2,线程创建的两种方式

    import threading
    import time
    
    
    def f1(*args):
        time.sleep(5)
        print(args)
    
    t = threading.Thread(target=f1, args=(123,))
    t.setDaemon(True)  # 是否设置为守护线程
    # 设置为True,表示主线程不等子线程执行完就结束整个程序
    # 设置为False,表示主线程等子线程结束后才会结束整个程序
    t.start()   # 一个线程
    第一种

    注意:其实上述线程的创建和调用在内部执行了,run()方法,自己动手去找一边。

    class MyThread(threading.Thread):
        def __init__(self, func, args):
            self.func = func
            self.args = args
            super(MyThread, self).__init__()
    
        def run(self):
            self.func(self.args)
    
    obj = MyThread(f1, 123)
    obj.start()
    第二种

    注意:第二种方式采用了自定义类的方式创建了线程,两种方法的本质其实都是一样的。都需要执行run()方法,第一种执行内部的run()方法,第二种执行自定义的run()方法。

    如果创建了多线程,到底那个是主线程?哪个又是子线程呢?

    import threading
    import time
    
    
    def f1(*args):  # 这个函数才会被子线程调用
        time.sleep(5)
        print(args, "子线程:", threading.current_thread()) 
    
    # 单进程,单线程
    for i in range(5):
        t = threading.Thread(target=f1, args=(123,))
        t.setDaemon(False)
        # 设置为True,表示主线程不等子线程执行完就结束整个程序;否则等子线程结束后才会结束整个程序
        t.start()   # 一个线程
    
        print("end")
    print("主线程",threading.current_thread())
    

      

      执行结果:

    end
    end
    end
    end
    end
    主线程 <_MainThread(MainThread, started 10600)>
    (123,) 子线程: <Thread(Thread-3, started 9612)>
    (123,) 子线程: <Thread(Thread-2, started 8808)>
    (123,) 子线程: <Thread(Thread-1, started 10824)>
    (123,) 子线程: <Thread(Thread-5, started 12092)>
    (123,) 子线程: <Thread(Thread-4, started 1684)>

    二, 队列

      1,先进先出   

    put放数据,是否阻塞(默认阻塞),阻塞时的超时时间
    get取数据,是否阻塞(默认阻塞),阻塞时的超时时间
    队列长度
    qsize()真实的个数
    maxsize 最大支持的个数
    join,task_done, join方法会阻塞进程,当队列中任务执行完毕之后,不再阻塞(即每次执行完任务,后来要加task_done)
    empty() 检查是否为空
    full() 检查是否已满

    例1.

    import queue
    q = queue.Queue(2)  # 里面参数表示最多可以放的元素的个数(默认为没有个数限制),如果超过这个个数,默认会阻塞。
    q.put(1)   # 放数据
    q.put(2)
    print(q.qsize())  # 队列中元素的个数
    q.put(6, block=False)  # 放数据时默认会阻塞,即block为True;否则,block为False时,如果放不进去,会直接报错!
    q.put(3, block=False, timeout=2)  # 后面还可以接超时时间,如果2秒后仍然放不进去,则直接报错!
    
    print(q.qsize())  # 队列中元素的个数
    print(q.get())  # 取数据
    

      

      例2.

    import queue
    q = queue.Queue(2)
    q.put(1)
    q.put(2)
    q.get()
    q.task_done()
    q.get()
    q.task_done()
    q.join()
    

      

    注意:这里是放到队列中两个数据,然后再取出两个数据,程序执行完毕,队列清空。如果直接再后面加上join,会阻塞程序。如果每次取出数据都告诉join(即在每次取完时在q.task_done),直到取完程序立刻终止,不会阻塞。用的比较少。

      2,其他队列

    queue.Queue() # 先进先出队列
    queue.deque() # 双向队列(两头进两头出)
    queue.LifoQueue() # 后进先出队列(last in first out)
    queue.PriorityQueue() # 优先级队列

    例1:先进后出

    import queque	
    q = queue.LifoQueue()  # 后进先出队列(last in first out)
    
    q.put(123)
    q.put(456)
    r = q.get()
    print(r)
    
    #执行结果:
    #456
    

      

      例2:优先级队列

    import queque
    q = queue.PriorityQueue()  # 优先级队列
    
    q.put((0, 'tom'))  # 两个参数,前面一个是优先级,后面一个是放进去的数据,优先级越高,先出
    q.put((5, 'lily'))
    q.put((2, 'yn'))
    r = q.get()
    print(r)
    
    #执行结果:
    #tom
    

      

      例3:双向队列

    import queue
    
    q = queue.deque()  # 双向队列(两头进两头出)
    
    q.append("hello")
    q.append("world")
    q.appendleft("tom")
    r = q.pop()
    r1 = q.popleft()
    print(r)
    print(r1)
    
    #执行结果:
    
    #world
    #tom
    

      注意:这些队列都是在python内存中创建的,程序退出队列清空!

    三,生产者和消费者模型

    比如:用户访问12306网站春运时并发非常的高,大量的用户来连接服务器很可能引起服务器的宕机。此时引入生产者消费者模型,就好比在中间加了个管道,如有大并发的连接,全部放入管道,等待服务器到管道获取连接进行排队处理,服务器处理完,用户在进行查询来获取处理结果。

    import queue
    import threading
    import time
    q = queue.Queue(maxsize=10)
    
    
    def producer():
        count = 0
        while True:
            q.put(count)
            print("生产了第{}个包子".format(count))
            count += 1
            time.sleep(1)
    
    
    def consumer(arg):
        while True:
            print("包子还剩{}个".format(q.qsize()))
            print("食客{}吃了第{}个包子".format(arg, q.get()))
            time.sleep(10)
    
    
    t = threading.Thread(target=producer, args=())
    c1 = threading.Thread(target=consumer, args=("tom",))
    c2 = threading.Thread(target=consumer, args=("lucy",))
    t.start()
    c1.start()
    c2.start()
    

      

    卖家不断生产包子,消费者不断吃包子。(分两种情况)
    如果卖家生产的速度大于消费者吃的速度,一直累计,如果剩余的包子达到了10个卖家就不再生产,等待消费者去吃,一旦小于10个包子,卖家再继续生产。
    如果卖家生产的速度小于消费者吃的速度,直到所有的包子吃完,消费者等待卖家去生产,消费者才能接着吃。

    1.  实现了生产者和消费者的隔离,即松耦合性;

    2.  解决了瞬间大并发的问题!

    四, 线程锁(互斥锁)

    import threading
    import time
    NUM = 10 
    
    
    def f1(l):
    	global NUM
    	# 上锁
    	l.acquire()
    	NUM -= 1
    	time.sleep(2)
    	print(NUM)
    	# 开锁
    	l.release()
    
    # lock = threading.Lock()
    lock = threading.RLock()
    
    for i in range(10):
    	t = threading.Thread(target=f1, args=(lock,))
    	t.start()
    

      

    总结:
    1,线程在操作系统执行的最小单元,同一个进程中的线程之间共享内存等各种资源,如何同一个进程中的
    很多线程都在修改同一个数据,就是造成数据的混乱,不统一的情况,所以需要线程锁,也叫互斥锁。这样
    线程每个线程在执行操作之前都在加锁,所以保证了数据的一致性。
    如果上述程序不加锁的输出为:10个0,正常输出应该是:9-0

    2,注意Rlock 和 lock的区别,Rlock为递归锁,锁多层,lock只能锁一次。Rlock里面支持多层锁的嵌套,
    一般最好使用Rlock,好处显而易见。

     

    五, 信号量 (semaphore)

    互斥锁,同时只允许一个线程更改数据,而semaphore是同时允许一定数量的线程更改数据,比如,有5个收费口高速公路收费站只允许同时5辆车进行缴费,前面5辆车过去,后面的车才能进来。

    import threading
    import time
    NUM = 10
    
    
    def f1(l):
    	global NUM
    	# 上锁
    	l.acquire()   # 这时每次只放行2个
    	NUM -= 1
    	time.sleep(2)
    	print(NUM)
    	# 开锁
    	l.release()
    
    # lock = threading.Lock()
    # lock = threading.RLock()
    lock = threading.BoundedSemaphore(2)  # 允许同时最多有2个同时运行
    for i in range(10):
    	t = threading.Thread(target=f1, args=(lock,))
    	t.start()
    

      

    六, 事件(event)

    python线程的事件用于主线程控制其他线程的执行,事件主要提供了三个方法 set、wait、clear。

    事件处理的机制:全局定义了一个“Flag”,如果“Flag”值为 False,那么当程序执行 event.wait 方法时就会阻塞,如果“Flag”值为True,那么event.wait 方法时便不再阻塞。
    clear:将“Flag”设置为False
    set:将“Flag”设置为True

    import threading
    
    
    def f1(i, e):
    	print(i)
    	e.wait()   # 检测是什么灯,如果是红灯,停;如果是绿灯,行。
    	print(i + 100)
    
    event = threading.Event()
    for i in range(10):
    	t = threading.Thread(target=f1, args=(i, event))
    	t.start()
    # ------------------
    event.clear()   # 设置为红灯,全部暂停
    inp = input('>>>')
    if inp == '1':
    	event.set()  # 设置为绿灯,设置为绿灯,全部放行。
    

      执行流程:首先10个线程首先print(i),然后阻塞,等待放行,然后一次性全部通过,执行print(i + 100)

    七, 条件(condition)

    让线程只有在满足某个条件的情况下才允许n个线程通过。

    import threading
    
    
    def f1(i, conn):
        print(i)
        conn.acquire()
        conn.wait()
        print(i + 100)
        conn.release()
    
    c = threading.Condition()
    for i in range(10):
        t = threading.Thread(target=f1, args=(i, c))
        t.start()
    
    while True:
        inp = input(">>>")
        if inp == 'q':
            break
        c.acquire()         # ---|
        c.notify(int(inp))  # ------> 注意这三句必须放在一起,为固定用法
        c.release()         # ---|
    第一种写法

    执行流程:首先10个线程首先print(i),接着线程阻塞在conn.acquire()这里,主线程接着向下执行,执行到input时,等待用户输入,当输入n,就会放行n个线程去执行print(i + 100)

    import threading
    
    def condition():
        ret = False
        r = input(">>>")
        if r == "true":
            ret = True
        else:
            ret = False
        return ret
    
    def f1(i, conn):
        print(i)
        conn.acquire()
        conn.wait_for(condition)
        print(i + 100)
        conn.release()
    
    c = threading.Condition()
    for i in range(10):
        t = threading.Thread(target=f1, args=(i, c))
        t.start()
    第二种写法

    和上述例子notify(1)的结果相同,只有满足某些条件才会执行。

    八, 定时器

    from threading import Timer
    
    def f1():
    	print("hello, world !")
    t = Timer(10, f1)
    t.start()

    等待10秒中才会执行f1函数,写监控,客户端等才会用到。

    九, 自定义线程池

    线程池非常重要,但是python2没有提供,python3中提供了这个功能,但是很弱。可以利用第三方库,最好自己去写!

    思路:维护一个容器(要有最大连接数),取一个少一个线程,无线程时等待,线程执行完毕,交还线程。需要用到队列!

    1,低版本的线程池

    import queue
    import threading
    import time
    
    
    class ThreadPoll:
    	def __init__(self, maxsize=5):
    		self.maxsize = maxsize
    		self._q = queue.Queue(maxsize)
    		for i in range(maxsize):
    			self._q.put(threading.Thread)
    			# [threading.Thread, threading.Thread, threading.Thread, threading.Thread]
    
    	def get_thread(self):
    		return self._q.get()
    
    	def add_thread(self):
    		self._q.put(threading.Thread)
    
    # 最多有5的允许5个线程
    poll = ThreadPoll(5)
    
    
    def task(args, p):
    	print(args)
    	time.sleep(1)
    	p.add_thread()
    	# 如果没有最后这个步的话,到第六个get_thread()会因为获取不到线程而阻塞住
    	# 所以在取走一个线程之后再添加一个线程。
    
    
    for i in range(100):
    	t = poll.get_thread()  # 此时的t为threading.Tread类
    	obj = t(target=task, args=(i, poll,))  # 创建一个threading.Tread对象
    	obj.start()
    

      

    执行流程:会5个5个的执行,注意线程执行没有先后顺序!

    分析:通过上述线程池会发现一些问题。
    a, 线程不能重复利用,只能开垃圾回收机制进行回收!
    b, 线程池直接开到了最大。当线程个数小于5个时,也会创建5个,导致线程的浪费!

      2,高级线程池

    #!/usr/bin/python
    # _*_ coding:utf-8 _*_
    import queue
    import threading
    import contextlib
    import time
    # 队列里面直接放任务,[(函数名,参数1),(函数名,参数2),(函数名,参数3)]
    
    
    StopEvent = object()
    
    
    class ThreadPool(object):
    	def __init__(self, max_num, max_task_num=None):
    		if max_task_num:
    			self.q = queue.Queue(max_task_num)
    		else:
    			self.q = queue.Queue()
    		self.max_num = max_num
    		self.cancel = False
    		self.terminal = False
    		self.generate_list = []  # 当前已经创建了多少线程
    		self.free_list = []  # 当前还空闲多少线程
    
    	def run(self, func, args, callback=None):
    		"""
    		线程池执行一个任务
    		:param func: 任务函数
    		:param args: 任务函数所需参数
    		:param callback: 任务执行失败或成功后执行的回调函数,回调函数有两个参数1、任务函数执行状态;2、任务函数返回值(默认为None,即:不执行回调函数)
    		:return: 如果线程池已经终止,则返回True否则None
    		"""
    		if self.cancel:
    			return
    		if len(self.free_list) == 0 and len(self.generate_list) < self.max_num:
    			# 当没有空闲的线程数,而且已经能够创建的线程数小于最大的线程数时,则创建线程
    			self.generate_thread()
    		w = (func, args, callback,)
    		self.q.put(w)  # 如果不满足上述判断的话,只把任务放在队列中而不会去创建线程
    
    	def generate_thread(self):
    		"""
    		创建一个线程
    		"""
    		t = threading.Thread(target=self.call)
    		t.start()
    
    	def call(self):
    		"""
    		循环去获取任务函数并执行任务函数
    		"""
    		current_thread = threading.currentThread
    		self.generate_list.append(current_thread)
    
    		event = self.q.get()
    		while event != StopEvent:
    
    			func, arguments, callback = event
    			try:
    				result = func(*arguments)
    				success = True
    			except Exception as e:
    				success = False
    				result = None
    
    			if callback is not None:
    				try:
    					callback(success, result)
    				except Exception as e:
    					pass
    
    			with self.worker_state(self.free_list, current_thread):
    				if self.terminal:
    					event = StopEvent
    				else:
    					event = self.q.get()
    		else:
    
    			self.generate_list.remove(current_thread)
    
    	def close(self):
    		"""
    		执行完所有的任务后,所有线程停止
    		"""
    		self.cancel = True
    		full_size = len(self.generate_list)
    		while full_size:
    			self.q.put(StopEvent)
    			full_size -= 1
    
    	def terminate(self):
    		"""
    		无论是否还有任务,终止线程
    		"""
    		self.terminal = True
    
    		while self.generate_list:
    			self.q.put(StopEvent)
    
    		self.q.empty()
    
    	@contextlib.contextmanager
    	def worker_state(self, state_list, worker_thread):
    		"""
    		用于记录线程中正在等待的线程数
    		"""
    		state_list.append(worker_thread)
    		try:
    			yield
    		finally:
    			state_list.remove(worker_thread)
    
    # ----------------执行--------------------------
    pool = ThreadPool(5)
    
    
    def callback(status, result):  # 定义的任务
    	# status, execute action status
    	# result, execute action return value
    	pass
    
    
    def action(i):
    	print(i)
    
    
    for i in range(300):
    	ret = pool.run(action, (i,), callback)
    
    # time.sleep(1)
    # print(len(pool.generate_list), len(pool.free_list))
    # print(len(pool.generate_list), len(pool.free_list))
    

      

    十, 进程基本的进程操作

    from multiprocessing import Process
    
    
    def f1(i):
    	print("hello,{}".format(i))
    
    if __name__ == '__main__':
    	# 注意在windows必须加这一段,否则会报错!但是,在linux,mac没有上述一行代码依然可以执行!
    	# 所以,在windows下慎用多进程!
    	for m in range(10):
    		p = Process(target=f1, args=("tom",))
    		# p.daemon = True
    		p.start()
    		# p.join()
    

      # 进程里面的这些方法和线程一样,这里不再一一赘述

    十一 进程的数据共享

    进程中默认数据是不共享的

    from multiprocessing import Process
    import threading
    import time
    
    
    def f1(i, args):
    	args.append(i)
    	print("hello,{}".format(i), args)
    
    if __name__ == '__main__':
    	# 注意在windows必须加这一段,否则会报错!但是,在linux,mac没有上述一行代码依然可以执行!
    	li = []
    	for m in range(5):
    		p = Process(target=f1, args=(m, li))
    		# p = threading.Thread(target=f1, args=(m, li))
    		# p.daemon = True
    		p.start()
    		# p.join()
    

      

    执行结果:

    hello,0 [0]
    hello,1 [1]
    hello,2 [2]
    hello,4 [4]
    hello,3 [3]
    由此可以看出进程之间,默认数据是不共享的。如果共享列表中的元素应该不断增加

     

    数据共享的种三种方式:

      a. 第一种,运用特殊的queques方式

      

    from multiprocessing import Process
    from multiprocessing import queues
    import multiprocessing
    
    
    def f1(i, args):
    	args.put(i)
    	print(args.qsize())
    
    if __name__ == '__main__':
    	li = queues.Queue(20, ctx=multiprocessing)
    	for m in range(5):
    		p = Process(target=f1, args=(m, li))
    		p.start()
    

      

    b. 第二种,运用数组的方式

    from multiprocessing import Process
    from multiprocessing import Array
    
    
    def f1(i, args):
    	args[i] = i + 100
    	for n in args:
    		print(n)
    	print("---------")
    
    
    if __name__ == '__main__':
    	li = Array('i', 6)   # 创建数组,规定好是字符型,数量为6个,这里的数组是C语言的格式
    	for m in range(5):
    		p = Process(target=f1, args=(m, li))
    		p.start()
    

    数组和链表的关系,列表就是基于链表来实现的。链表在内存中的数据不一定是连续的,但是每一个数据块都会记录上一个和下一个块的位置。而数组则在内存中的数据块是连续,类型、数量都是规定好的。注意所有语言里面的数据都是一样的。这种方式不常用。

      c. 第三种方式,利用特殊字典的方式

    from multiprocessing import Process
    from multiprocessing import Manager
    
    
    def f1(i, args):
    	args[i] = i + 100
    	print(args.values())
    
    if __name__ == '__main__':
    	obj = Manager()
    	li = obj.dict()
    	for m in range(5):
    		p = Process(target=f1, args=(m, li))
    		p.start()
    		p.join()    # 重要
    

    注意,最后的p.join()必须加上,否则会报错!主进程执行完之后会停止,而此时创的子进程需要修改主进程的数据,而此时主进程已经关闭。这种方式比较常用!

    十二, 进程锁

    from multiprocessing import Process, Array
    from multiprocessing import RLock, Lock, Event, Condition, Semaphore
    import time
    
    
    def f1(i, lis, lc):
    	lc.acquire()
    	lis[0] = lis[0] - 1
    	time.sleep(1)
    	print('hello,',lis[0])
    	lc.release()
    if __name__ == '__main__':
    	li = Array('i', 1)
    	li[0] = 10
    	lock = RLock()
    	for m in range(5):
    		p = Process(target=f1, args=(m, li, lock))
    		p.start()
    		p.join()
    

      

    注意:如果没有进程锁,多个子进程都要修改数据,会造成混乱,需要加锁

    总结:进程中同样有RLock, Lock, Event, Condition, Semaphore这个方法,和线程相同这里不再一一赘述!

    十三, 进程池

    进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进进程,那么程序就会等待,直到进程池中有可用进程为止。进程池python给定义好了,不需要自己去定义,只需要会用即可。

    from multiprocessing import Pool
    import time
    
    def f1(args):
    	print(args)
    
    if __name__ == '__main__':
    	pool = Pool(5)
    	for i in range(30):
    		# pool.apply(func=f1, args=(i,))  # 这是串行来执行的
    		pool.apply_async(func=f1, args=(i, ))  # 这是异步执行的
    	pool.close()  # 所有的任务执行完毕,才关闭
    	# time.sleep(1)
    	# pool.terminate()  # 立即终止当前任务!
    	pool.join() # 等待所有的任务执行完毕才会终止
    

      注意:close和terminate的区别,以及串行和异步的区别

    十四, 协程(coroutine)

    IO密集型用多线程,计算或者CPU密集型用多进程。如果要写爬虫,会产生http请求,http请求也叫IO请求,用多线程比较合适!而对于IO密集型的也比较适合用协程(协程不适合有大量cpu操作的)

    注意:线程和进程都是计算机给提供的,而协程则是在程序级别做的。

    原理:利用一个线程,分解一个线程为多个微线程。

    greenlet 底层的
    gevent 高级的,是对greenlet的封装

    那么到底协程的优势到底在哪里呢?

    协程最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。

    第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。

    因为协程是一个线程执行,那怎么利用多核CPU呢?最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。

    例1,简单的协程操作-greenlet

    from greenlet import greenlet
    
    
    def test1():
    	print(12)
    	gr2.switch()
    	print(34)
    	gr2.switch()
    
    
    def test2():
    	print(56)
    	gr1.switch()
    	print(78)
    
    
    gr1 = greenlet(test1)
    gr2 = greenlet(test2)
    gr1.switch()
    

      

    执行结果:(来回切换进行操作)

    12
    56
    34
    78

    例2:简单的协程操作-gevent

    import gevent
    
    
    def f1():
    	print("welcome to f1")
    	gevent.sleep(0)
    	print("welcome to f1 agin !")
    
    
    def f2():
    	print("welcome to f2")
    	gevent.sleep(0)
    	print("welcome to f2 agin !")
    
    gevent.joinall([gevent.spawn(f1), gevent.spawn(f2)])
    

      

    执行结果:

    welcome to f1
    welcome to f2
    welcome to f1 agin !
    welcome to f2 agin !

    例3:涉及到IO操作,gevent就是一个高性能的代名词。

    遇到IO阻塞时会自动切换任务

    #!/usr/bin/python
    # _*_ coding:utf-8 _*_
    import time
    import gevent
    import requests
    from gevent import monkey
    monkey.patch_all()
    # 把原来的socket功能修改了,发完请求的会告诉你发送完了,用上它,才能用协程,否则程序仍将串行执行
    
    
    def f1(url):
        print("GET %s" % url)
        resp = requests.get(url)
        data = resp.text
        print("%d bytes received from %s." % (len(data), url))
    
    urls = ["https://www.baidu.com/", "https://www.python.org/", "https://www.sina.com/"]
    t1 = time.time()
    for url in urls:
        f1(url)
    print("串行耗时:", time.time() - t1)
    
    t2 = time.time()
    gevent.joinall([
        gevent.spawn(f1, "https://www.baidu.com/"),
        gevent.spawn(f1, "https://www.python.org/"),
        gevent.spawn(f1, "https://www.sina.com/"),
    ])
    print("并行耗时:", time.time() - t2)
    

      执行结果:

    GET https://www.baidu.com/
    2443 bytes received from https://www.baidu.com/.
    GET https://www.python.org/
    47412 bytes received from https://www.python.org/.
    GET https://www.sina.com/
    610222 bytes received from https://www.sina.com/.
    串行耗时: 3.359192132949829
    GET https://www.baidu.com/
    GET https://www.python.org/
    GET https://www.sina.com/
    2443 bytes received from https://www.baidu.com/.
    610219 bytes received from https://www.sina.com/.
    47412 bytes received from https://www.python.org/.
    并行耗时: 1.083061933517456

     

     总结:并发的核心思想在于,大的任务可以分解成一系列的子任务,后者可以被调度成 同时执行或异步执行,而不是一次一个地或者同步地执行。两个子任务之间的 切换也就是上下文切换

    十五, 缓存

    python对memcache 和 redis 操作,详见:http://www.cnblogs.com/wupeiqi/articles/5132791.html

    1,安装软件
    2,程序:安装其对应的客户端(API)

     

     如果觉得好 ,请点赞支持一下哦!

  • 相关阅读:
    LeetCode 1122. Relative Sort Array (数组的相对排序)
    LeetCode 46. Permutations (全排列)
    LeetCode 47. Permutations II (全排列 II)
    LeetCode 77. Combinations (组合)
    LeetCode 1005. Maximize Sum Of Array After K Negations (K 次取反后最大化的数组和)
    LeetCode 922. Sort Array By Parity II (按奇偶排序数组 II)
    LeetCode 1219. Path with Maximum Gold (黄金矿工)
    LeetCode 1029. Two City Scheduling (两地调度)
    LeetCode 392. Is Subsequence (判断子序列)
    写程序判断系统是大端序还是小端序
  • 原文地址:https://www.cnblogs.com/yang-ning/p/6426210.html
Copyright © 2011-2022 走看看