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)

     

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

  • 相关阅读:
    (转)在IOS应用中从竖屏模式强制转换为横屏模式
    IOS设备屏幕旋转相关技术收集
    超级工厂模式
    (转) objectiveC中的接口@interface与泛型(id)
    (原创)IOS播放视频
    IOS检测网络连接状态(转)
    (转) MPMoviePlayerController 电影播放器—IOS开发
    Oracle分析表和索引
    How To Partition Existing Table Using DBMS_Redefinition
    不可用索引
  • 原文地址:https://www.cnblogs.com/yang-ning/p/6426210.html
Copyright © 2011-2022 走看看