四 协程

  1. 协程,又称微线程,纤程。英文名Coroutine。一句话说明什么是线程:协程是一种用户态的轻量级线程。
  2.  
  3. 协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:
  4.  
  5. 协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。

协程

4.1 yield与协程

  1. import time
  2.  
  3. """
  4. 传统的生产者-消费者模型是一个线程写消息,一个线程取消息,通过锁机制控制队列和等待,但一不小心就可能死锁。
  5. 如果改用协程,生产者生产消息后,直接通过yield跳转到消费者开始执行,待消费者执行完毕后,切换回生产者继续生产,效率极高。
  6. """
  7. # 注意到consumer函数是一个generator(生成器):
  8. # 任何包含yield关键字的函数都会自动成为生成器(generator)对象
  9.  
  10. def consumer():
  11. r = ''
  12. while True:
  13. # 3、consumer通过yield拿到消息,处理,又通过yield把结果传回;
  14. # yield指令具有return关键字的作用。然后函数的堆栈会自动冻结(freeze)在这一行。
  15. # 当函数调用者的下一次利用next()或generator.send()或for-in来再次调用该函数时,
  16. # 就会从yield代码的下一行开始,继续执行,再返回下一次迭代结果。通过这种方式,迭代器可以实现无限序列和惰性求值。
  17. n = yield r
  18. if not n:
  19. return
  20. print('[CONSUMER] ←← Consuming %s...' % n)
  21. time.sleep(1)
  22. r = '200 OK'
  23. def produce(c):
  24. # 1、首先调用c.next()启动生成器
  25. next(c)
  26. n = 0
  27. while n < 5:
  28. n = n + 1
  29. print('[PRODUCER] →→ Producing %s...' % n)
  30. # 2、然后,一旦生产了东西,通过c.send(n)切换到consumer执行;
  31. cr = c.send(n)
  32. # 4、produce拿到consumer处理的结果,继续生产下一条消息;
  33. print('[PRODUCER] Consumer return: %s' % cr)
  34. # 5、produce决定不生产了,通过c.close()关闭consumer,整个过程结束。
  35. c.close()
  36. if __name__=='__main__':
  37. # 6、整个流程无锁,由一个线程执行,produce和consumer协作完成任务,所以称为“协程”,而非线程的抢占式多任务。
  38. c = consumer()
  39. produce(c)
  40.  
  41. '''
  42. result:
  43.  
  44. [PRODUCER] →→ Producing 1...
  45. [CONSUMER] ←← Consuming 1...
  46. [PRODUCER] Consumer return: 200 OK
  47. [PRODUCER] →→ Producing 2...
  48. [CONSUMER] ←← Consuming 2...
  49. [PRODUCER] Consumer return: 200 OK
  50. [PRODUCER] →→ Producing 3...
  51. [CONSUMER] ←← Consuming 3...
  52. [PRODUCER] Consumer return: 200 OK
  53. [PRODUCER] →→ Producing 4...
  54. [CONSUMER] ←← Consuming 4...
  55. [PRODUCER] Consumer return: 200 OK
  56. [PRODUCER] →→ Producing 5...
  57. [CONSUMER] ←← Consuming 5...
  58. [PRODUCER] Consumer return: 200 OK
  59. '''

4.2 greenlet

greenlet机制的主要思想是:生成器函数或者协程函数中的yield语句挂起函数的执行,直到稍后使用next()或send()操作进行恢复为止。可以使用一个调度器循环在一组生成器函数之间协作多个任务。greentlet是python中实现我们所谓的"Coroutine(协程)"的一个基础库.

  1. from greenlet import greenlet
  2.  
  3. def test1():
  4. print (12)
  5. gr2.switch()
  6. print (34)
  7. gr2.switch()
  8.  
  9. def test2():
  10. print (56)
  11. gr1.switch()
  12. print (78)
  13.  
  14. gr1 = greenlet(test1)
  15. gr2 = greenlet(test2)
  16. gr1.switch()

4.2 基于greenlet的框架

4.2.1 gevent模块实现协程

Python通过yield提供了对协程的基本支持,但是不完全。而第三方的gevent为Python提供了比较完善的协程支持。

gevent是第三方库,通过greenlet实现协程,其基本思想是:

当一个greenlet遇到IO操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO。

由于切换是在IO操作时自动完成,所以gevent需要修改Python自带的一些标准库,这一过程在启动时通过monkey patch完成:

  1. import gevent
  2. import time
  3.  
  4. def foo():
  5. print("running in foo")
  6. gevent.sleep(2)
  7. print("switch to foo again")
  8.  
  9. def bar():
  10. print("switch to bar")
  11. gevent.sleep(5)
  12. print("switch to bar again")
  13.  
  14. start=time.time()
  15.  
  16. gevent.joinall(
  17. [gevent.spawn(foo),
  18. gevent.spawn(bar)]
  19. )
  20.  
  21. print(time.time()-start)

当然,实际代码里,我们不会用gevent.sleep()去切换协程,而是在执行到IO操作时,gevent自动切换,代码如下:

  1. from gevent import monkey
  2. monkey.patch_all()
  3. import gevent
  4. from urllib import request
  5. import time
  6.  
  7. def f(url):
  8. print('GET: %s' % url)
  9. resp = request.urlopen(url)
  10. data = resp.read()
  11. print('%d bytes received from %s.' % (len(data), url))
  12.  
  13. start=time.time()
  14.  
  15. gevent.joinall([
  16. gevent.spawn(f, 'https://itk.org/'),
  17. gevent.spawn(f, 'https://www.github.com/'),
  18. gevent.spawn(f, 'https://zhihu.com/'),
  19. ])
  20.  
  21. # f('https://itk.org/')
  22. # f('https://www.github.com/')
  23. # f('https://zhihu.com/')
  24.  
  25. print(time.time()-start)

eventlet实现协程(了解)

  1. '''
  2. eventlet 是基于 greenlet 实现的面向网络应用的并发处理框架,提供“线程”池、队列等与其他 Python 线程、进程模型非常相似的 api,并且提供了对 Python 发行版自带库及其他模块的超轻量并发适应性调整方法,比直接使用 greenlet 要方便得多。
  3.  
  4. 其基本原理是调整 Python 的 socket 调用,当发生阻塞时则切换到其他 greenlet 执行,这样来保证资源的有效利用。需要注意的是:
  5. eventlet 提供的函数只能对 Python 代码中的 socket 调用进行处理,而不能对模块的 C 语言部分的 socket 调用进行修改。对后者这类模块,仍然需要把调用模块的代码封装在 Python 标准线程调用中,之后利用 eventlet 提供的适配器实现 eventlet 与标准线程之间的协作。
  6. 虽然 eventlet 把 api 封装成了非常类似标准线程库的形式,但两者的实际并发执行流程仍然有明显区别。在没有出现 I/O 阻塞时,除非显式声明,否则当前正在执行的 eventlet 永远不会把 cpu 交给其他的 eventlet,而标准线程则是无论是否出现阻塞,总是由所有线程一起争夺运行资源。所有 eventlet 对 I/O 阻塞无关的大运算量耗时操作基本没有什么帮助。
  7. '''

总结

协程的好处:

无需线程上下文切换的开销
无需原子操作锁定及同步的开销
方便切换控制流,简化编程模型
高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。
缺点:

无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上.当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。
进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序

五 IO模型

  1. '''
  2. 同步(synchronous) IO和异步(asynchronous) IO,阻塞(blocking) IO和非阻塞(non-blocking)IO分别是什么,到底有什么区别?这个问题其实不同的人给出的答案都可能不同,比如wiki,就认为asynchronous IO和non-blocking IO是一个东西。这其实是因为不同的人的知识背景不同,并且在讨论这个问题的时候上下文(context)也不相同。所以,为了更好的回答这个问题,先限定一下本文的上下文。
  3. 本文讨论的背景是Linux环境下的network IO。
  4.  
  5. Stevens在文章中一共比较了五种IO Model:
  6.  
  7. blocking IO
  8. nonblocking IO
  9. IO multiplexing
  10. signal driven IO
  11. asynchronous IO
  12. 由于signal driven IO在实际中并不常用,所以我这只提及剩下的四种IO Model。
  13. 再说一下IO发生时涉及的对象和步骤。
  14. 对于一个network IO (这里我们以read举例),它会涉及到两个系统对象,一个是调用这个IO的process (or thread),另一个就是系统内核(kernel)。当一个read操作发生时,它会经历两个阶段:
  15.  
  16. 等待数据准备 (Waiting for the data to be ready)
  17. 将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)
  18. 记住这两点很重要,因为这些IO Model的区别就是在两个阶段上各有不同的情况。
  19. '''

selectors模块

  1. import selectors
  2. import socket
  3.  
  4. sel = selectors.DefaultSelector()
  5.  
  6. def accept(sock, mask):
  7. conn, addr = sock.accept() # Should be ready
  8. print('accepted', conn, 'from', addr)
  9. conn.setblocking(False)
  10. sel.register(conn, selectors.EVENT_READ, read)
  11.  
  12. def read(conn, mask):
  13. data = conn.recv(1000) # Should be ready
  14. if data:
  15. print('echoing', repr(data), 'to', conn)
  16. conn.send(data) # Hope it won't block
  17. else:
  18. print('closing', conn)
  19. sel.unregister(conn)
  20. conn.close()
  21.  
  22. sock = socket.socket()
  23. sock.bind(('localhost', 1234))
  24. sock.listen(100)
  25. sock.setblocking(False)
  26. sel.register(sock, selectors.EVENT_READ, accept)
  27.  
  28. while True:
  29. events = sel.select()
  30. for key, mask in events:
  31. callback = key.data
  32. callback(key.fileobj, mask)

5.1 blocking IO (阻塞IO)

在linux中,默认情况下所有的socket都是blocking,一个典型的读操作流程大概是这样:

  1. '''
  2. 当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据。对于network io来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的UDP包),这个时候kernel就要等待足够的数据到来。而在用户进程这边,整个进程会被阻塞。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除block的状态,重新运行起来。
  3. 所以,blocking IO的特点就是在IO执行的两个阶段都被block了。
  4. '''

5.2 non-blocking IO(非阻塞IO)

linux下,可以通过设置socket使其变为non-blocking。当对一个non-blocking socket执行读操作时,流程是这个样子:

  1. '''
  2. 从图中可以看出,当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。所以,用户进程其实是需要不断的主动询问kernel数据好了没有。
  3.  
  4. 注意:
  5.  
  6. 在网络IO时候,非阻塞IO也会进行recvform系统调用,检查数据是否准备好,与阻塞IO不一样,”非阻塞将大的整片时间的阻塞分成N多的小的阻塞, 所以进程不断地有机会 ‘被’ CPU光顾”。即每次recvform系统调用之间,cpu的权限还在进程手中,这段时间是可以做其他事情的,
  7.  
  8. 也就是说非阻塞的recvform系统调用调用之后,进程并没有被阻塞,内核马上返回给进程,如果数据还没准备好,此时会返回一个error。进程在返回之后,可以干点别的事情,然后再发起recvform系统调用。重复上面的过程,循环往复的进行recvform系统调用。这个过程通常被称之为轮询。轮询检查内核数据,直到数据准备好,再拷贝数据到进程,进行数据处理。需要注意,拷贝数据整个过程,进程仍然是属于阻塞的状态。
  9. '''
  10. #############################server
  11.  
  12. import time
  13. import socket
  14. sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
  15. sk.setsockopt
  16. sk.bind(('127.0.0.1',6667))
  17. sk.listen(5)
  18. sk.setblocking(False) #非阻塞IO
  19. while True:
  20. try:
  21. print ('waiting client connection .......')
  22. connection,address = sk.accept() # 进程主动轮询,没有信息报错,用try、except捕获,并继续轮训
  23. print("+++",address)
  24. client_messge = connection.recv(1024)
  25. print(str(client_messge,'utf8'))
  26. connection.close()
  27. except Exception as e:
  28. print (e)
  29. time.sleep(4)
  30.  
  31. #############################client
  32.  
  33. import time
  34. import socket
  35. sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
  36.  
  37. while True:
  38. sk.connect(('127.0.0.1',6667))
  39. print("hello")
  40. sk.sendall(bytes("hello","utf8"))
  41. time.sleep(2)
  42. break
  43.  
  44. '''
  45. 优点:能够在等待任务完成的时间里干其他活了(包括提交其他任务,也就是 “后台” 可以有多个任务在同时执行)。
  46.  
  47. 缺点:任务完成的响应延迟增大了,因为每过一段时间才去轮询一次read操作,而任务可能在两次轮询之间的任意时间完成。这会导致整体数据吞吐量的降低。
  48. '''

5.3 IO multiplexing(IO多路复用)

select的优势在于可以处理多个连接,不适用于单个连接

IO multiplexing这个词可能有点陌生,但是如果我说select,epoll,大概就都能明白了。有些地方也称这种IO方式为event driven IO。我们都知道,select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。它的基本原理就是select/epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。它的流程如图:

  1. '''
  2. select(什么平台都有,Windows只能用这个,有最大链接上限,轮训(一定得走完一圈))
  3. pool(没有链接上限,轮训(一定得走完一圈))
  4.  
  5. epoll(推荐,没有最大链接上限,不是轮训,是对象主动触发回调函数)
  6. '''
  1. '''
  2. 当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。
  3. 这个图和blocking IO的图其实并没有太大的不同,事实上,还更差一些。因为这里需要使用两个system call (select 和 recvfrom),而blocking IO只调用了一个system call (recvfrom)。但是,用select的优势在于它可以同时处理多个connection。(多说一句。所以,如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。)
  4. 在IO multiplexing Model中,实际中,对于每一个socket,一般都设置成为non-blocking,但是,如上图所示,整个用户的process其实是一直被block的。只不过process是被select这个函数block,而不是被socket IO给block。
  5.  
  6. 结论: select的优势在于可以处理多个连接,不适用于单个连接
  7. '''
  8. # conn,addr=sock.accept() #默认是阻塞方式,等待客户端连接(accept 做了两件事,监听内存空间,拷贝收到的信息)
  9.  
  10. #***********************server.py
  11.  
  12. import socket
  13. import select
  14. sk=socket.socket()
  15. sk.bind(("127.0.0.1",8800))
  16. sk.listen(5)
  17. sk.setblocking(False)
  18. inputs=[sk,] ##监听的套接字对象的列表
  19.  
  20. while True:
  21. r,w,e=select.select(inputs,[],[],5) #收,发,错误 三个列表,得到的r 也是列表 (只做了 监听工作,后面还得拷贝信息)
  22. print(len(r))
  23.  
  24. for obj in r: #r是监听的活动的socket
  25. if obj==sk: #sock 只是接收用户的链接信息,sock.accept()得到的conn才能接收客户端的之后发过来的具体信息
  26. conn,add=obj.accept()
  27. print("conn:",conn)
  28. inputs.append(conn)
  29. else:
  30.  
  31. data_byte=obj.recv(1024)
  32. print(str(data_byte,'utf8'))
  33. if not data_byte:
  34. inputs.remove(obj)
  35. continue
  36. inp=input('回答%s: >>>'%inputs.index(obj))
  37. obj.sendall(bytes(inp,'utf8'))
  38.  
  39. print('>>',r)
  40.  
  41. #***********************client.py
  42.  
  43. import socket
  44. sk=socket.socket()
  45. sk.connect(('127.0.0.1',8802))
  46.  
  47. while True:
  48. inp=input(">>>>") # how much one night?
  49. sk.sendall(bytes(inp,"utf8"))
  50. data=sk.recv(1024)
  51. print(str(data,'utf8'))
  52.  
  53. '''
  54. 思考1:select监听fd变化的过程
  55.  
  56. 用户进程创建socket对象,拷贝监听的fd到内核空间,每一个fd会对应一张系统文件表,内核空间的fd响应到数据后,就会发送信号给用户进程数据已到;用户进程再发送系统调用,比如(accept)将内核空间的数据copy到用户空间,同时作为接受数据端内核空间的数据清除,这样重新监听时fd再有新的数据又可以响应到了(发送端因为基于TCP协议所以需要收到应答后才会清除)。
  57.  
  58. 思考2: 上面的示例中,开启三个客户端,分别连续向server端发送一个内容(中间server端不回应),结果会怎样,为什么?(只显示第一个信息,回复之后第二个,之后第三个)
  59. '''

 5.4 Asynchronous I/O(异步IO)

linux下的asynchronous IO其实用得很少。先看一下它的流程:

  1. '''
  2. 用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。
  3. '''

5.5 IO模型比较分析

各个IO Model的比较如图所示:

      

  1. 到目前为止,已经将四个IO Model都介绍完了。现在回过头来回答最初的那几个问题:blockingnon-blocking的区别在哪,synchronous IOasynchronous IO的区别在哪。
  2. 先回答最简单的这个:blocking vs non-blocking。前面的介绍中其实已经很明确的说明了这两者的区别。调用blocking IO会一直block住对应的进程直到操作完成,而non-blocking IOkernel还准备数据的情况下会立刻返回。
  3.  
  4. 在说明synchronous IOasynchronous IO的区别之前,需要先给出两者的定义。Stevens给出的定义(其实是POSIX的定义)是这样子的:
  5. A synchronous I/O operation causes the requesting process to be blocked until that I/O operationcompletes;
  6. An asynchronous I/O operation does not cause the requesting process to be blocked;
  7. 两者的区别就在于synchronous IO做”IO operation”的时候会将process阻塞。按照这个定义,之前所述的blocking IOnon-blocking IOIO multiplexing都属于synchronous IO。有人可能会说,non-blocking IO并没有被block啊。这里有个非常“狡猾”的地方,定义中所指的”IO operation”是指真实的IO操作,就是例子中的recvfrom这个system callnon-blocking IO在执行recvfrom这个system call的时候,如果kernel的数据没有准备好,这时候不会block进程。但是,当kernel中数据准备好的时候,recvfrom会将数据从kernel拷贝到用户内存中,这个时候进程是被block了,在这段时间内,进程是被block的。而asynchronous IO则不一样,当进程发起IO 操作之后,就直接返回再也不理睬了,直到kernel发送一个信号,告诉进程说IO完成。在这整个过程中,进程完全没有被block
  8.  
  9. 经过上面的介绍,会发现non-blocking IOasynchronous IO的区别还是很明显的。在non-blocking IO中,虽然进程大部分时间都不会被block,但是它仍然要求进程去主动的check,并且当数据准备完成以后,也需要进程主动的再次调用recvfrom来将数据拷贝到用户内存。而asynchronous IO则完全不同。它就像是用户进程将整个IO操作交给了他人(kernel)完成,然后他人做完后发信号通知。在此期间,用户进程不需要去检查IO操作的状态,也不需要主动的去拷贝数据。

Python(线程进程3)的更多相关文章

  1. python 线程 进程

    1.进程与线程优.缺点的比较总言:使用进程和线程的目的,提高执行效率. 进程: 优点:能利用机器的多核性能,同时进行多个操作. 缺点:需要耗费资源,重新开辟内存空间,耗内存. 线程: 优点:共享内存( ...

  2. python 线程 进程 协程 学习

    转载自大神博客:http://www.cnblogs.com/aylin/p/5601969.html 仅供学习使用···· python 线程与进程简介 进程与线程的历史 我们都知道计算机是由硬件和 ...

  3. python线程进程

    多道技术: 多道程序设计技术 所谓多道程序设计技术,就是指允许多个程序同时进入内存并运行.即同时把多个程序放入内存,并允许它们交替在CPU中运行,它们共享系统中的各种硬.软件资源.当一道程序因I/O请 ...

  4. Python 线程&进程与协程

    Python 的创始人为吉多·范罗苏姆(Guido van Rossum).1989年的圣诞节期间,吉多·范罗苏姆为了在阿姆斯特丹打发时间,决心开发一个新的脚本解释程序,作为ABC语言的一种继承.Py ...

  5. python 线程进程

      一 线程的2种调用方式 直接调用 实例1: import threading import time def sayhi(num): #定义每个线程要运行的函数 print("runni ...

  6. python线程,进程,队列和缓存

    一.线程 threading用于提供线程相关的操作,线程是应用程序中工作的最小单元. 创建线程的两种方式1.threading.Thread import threading def f1(arg): ...

  7. python 线程,进程28原则

    基于函数实现 from threading import Thread def fun(data, *args, **kwargs): """ :param data: ...

  8. python 线程/进程模块

    线程的基本使用: import threading # ###################### 1.线程的基本使用 def func(arg): print(arg) t = threading ...

  9. python 线程 进程 标识

    s = '%s%s%s%s%s%s%s%s' % ( time.strftime('%Y%m%d %H:%M:%S', time.localtime(time.time())), ' os.getpp ...

  10. python 线程(一)理论部分

    Python线程 进程有很多优点,它提供了多道编程,可以提高计算机CPU的利用率.既然进程这么优秀,为什么还要线程呢?其实,仔细观察就会发现进程还是有很多缺陷的. 主要体现在一下几个方面: 进程只能在 ...

随机推荐

  1. java----内部类与匿名内部类的各种注意事项与知识点

    Java 内部类分四种:成员内部类.局部内部类.静态内部类和匿名内部类.1.成员内部类: 即作为外部类的一个成员存在,与外部类的属性.方法并列.注意:成员内部类中不能定义静态变量,但可以访问外部类的所 ...

  2. MFC通过button控制编辑框是否显示系统时间(动态显示)

    1.在dlg.h中public bool flag; static UINT time(void *param); 2.在构造函数中 flag=false; 3.在button的生成函数中 if(fl ...

  3. thinkphp nginx pathinfo模式支持

    最近一个项目中使用了ThinkPHP做为开发框架,URL上我们使用了PATHINFO模式,但是Nginx默认是不支持PATHINFO的,需要进行手动配置才可以,于是我们按照了以下方法进行了Nginx的 ...

  4. ThinkPHP项目笔记之RBAC(权限)下篇

    接着谈谈:添加用户以及用户管理列表 e.添加用户

  5. ASP.NET MVC5 新特性:Attribute路由使用详解

    1.什么是Attribute路由?怎么样启用Attribute路由? 微软在 ASP.NET MVC5 中引入了一种新型路由:Attribute路由,顾名思义,Attribute路由是通过Attrib ...

  6. Android popupwindow 演示样例程序一

    经过多番測试实践,实现了popupwindow 弹出在指定控件的下方.代码上有凝视.有须要注意的地方.popupwindow 有自已的布局,里面控件的监听实现都有.接下来看代码实现. 项目资源下载:点 ...

  7. aar

    aar是一个类似于jar的文件格式.但是他们之间是有区别的.jar:仅仅包含class和清单文件,没有资源文件.aar:包含了class文件和资源文件.说白了就是Android的专属“jar” 将代码 ...

  8. 在 Linux 下使用任务管理器

    有很多 Linux 初学者经常问起的问题,“Linux 有任务管理器吗?”,“怎样在 Linux 上打开任务管理器呢?” 来自 Windows 的用户都知道任务管理器非常有用.你可以在 Windows ...

  9. docker harbor 安装 使用总结

    总结:没有验证,但是猜测. 我这个harbor的机器上  有起了一个 docker的 registry, 5000端口的,不知道是不是二者冲突. 猜测是这个情况. 1. 安装参考 收藏的链接 1.1  ...

  10. Vue.js_getter and setter

    computed 计算属性: 1.get 读取 <div id="test2"> <input type="text" v-model=&qu ...