python2协程_python中的协程(二)

协程

1、协程:

单线程实现并发

在应用程序里控制多个任务的切换+保存状态

优点:

应用程序级别速度要远远高于操作系统的切换

缺点:

多个任务一旦有一个阻塞没有切,整个线程都阻塞在原地,该线程内的其他的任务都不能执行了

一旦引入协程,就需要检测单线程下所有的IO行为, 实现遇到IO就切换,少一个都不行,以为一旦一个任务阻塞了,整个线程就阻塞了, 其他的任务即便是可以计算,但是也无法运行了

2、协程序的目的:

想要在单线程下实现并发

并发指的是多个任务看起来是同时运行的

并发=切换+保存状态

协程引入

cpu正在运行一个任务,会在两种情况下切走去执行其他的任务(切换由操作系统强制控制),一种情况是该任务发生了阻塞,另外一种情况是该任务计算的时间过长或有一个优先级更高的程序替代了它

ps:在介绍进程理论时,提及进程的三种执行状态,而线程才是执行单位,所以也可以将上图理解为线程的三种状态

一:其中第二种情况并不能提升效率,只是为了让cpu能够雨露均沾,实现看起来所有任务都被“同时”执行的效果,如果多个任务都是纯计算的,这种切换反而会降低效率。为此我们可以基于yield来验证。yield本身就是一种在单线程下可以保存任务运行状态的方法,我们来简单复习一下:

#串行执行

import time

def func1():

for i in range(10000000):

i+1

def func2():

for i in range(10000000):

i+1

start = time.time()

func1()

func2()

stop = time.time()

print(stop - start)

#基于yield并发执行

import time

def func1():

while True:

yield

def func2():

g=func1()

for i in range(10000000):

i+1

next(g)

start=time.time()

func2()

stop=time.time()

print(stop-start)

二:第一种情况的切换。在任务一遇到io情况下,切到任务二去执行,这样就可以利用任务一阻塞的时间完成任务二的计算,效率的提升就在于此。

import time

def func1():

while True:

print('func1')

yield

def func2():

g=func1()

for i in range(10000000):

i+1

next(g)

time.sleep(3)

print('func2')

start=time.time()

func2()

stop=time.time()

print(stop-start)

yield不能检测IO,实现遇到IO自动切换

对于单线程下,我们不可避免程序中出现io操作,但如果我们能在自己的程序中(即用户程序级别,而非操作系统级别)控制单线程下的多个任务能在一个任务遇到io阻塞时就切换到另外一个任务去计算,这样就保证了该线程能够最大限度地处于就绪态,即随时都可以被cpu执行的状态,相当于我们在用户程序级别将自己的io操作最大限度地隐藏起来,从而可以迷惑操作系统,让其看到:该线程好像是一直在计算,io比较少,从而更多的将cpu的执行权限分配给我们的线程。

协程的本质:

就是在单线程下,由用户自己控制一个任务遇到io阻塞了就切换另外一个任务去执行,以此来提升效率。为了实现它,我们需要找寻一种可以同时满足以下条件的解决方案:

#1. 可以控制多个任务之间的切换,切换之前将任务的状态保存下来,以便重新运行时,可以基于暂停的位置继续执行。

#2. 作为1的补充:可以检测io操作,在遇到io操作的情况下才发生切换

协程介绍

协程:是单线程下的并发,又称微线程,纤程。英文名Coroutine。一句话说明什么是线程:协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的。、

需要强调的是:

#1. python的线程属于内核级别的,即由操作系统控制调度(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其他线程运行)

#2. 单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率(!!!非io操作的切换与效率无关)

对比操作系统控制线程的切换,用户在单线程内控制协程的切换

优点如下:

#1. 协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级

#2. 单线程内就可以实现并发的效果,最大限度地利用cpu

缺点如下:

#1. 协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程

#2. 协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程

总结协程特点:

必须在只有一个单线程里实现并发

修改共享数据不需加锁

用户程序里自己保存多个控制流的上下文栈

附加:一个协程遇到IO操作自动切换到其它协程(如何实现检测IO,yield、greenlet都无法实现,就用到了gevent模块(select机制))

Greenlet

如果我们在单个线程内有20个任务,要想实现在多个任务之间切换,使用yield生成器的方式过于麻烦(需要先得到初始化一次的生成器,然后再调用send。。。非常麻烦),而使用greenlet模块可以非常简单地实现这20个任务直接的切换

#安装

pip3 install greenlet

from greenlet import greenlet

def eat(name):

print('%s eat 1' %name)

g2.switch('egon')

print('%s eat 2' %name)

g2.switch()

def play(name):

print('%s play 1' %name)

g1.switch()

print('%s play 2' %name)

g1=greenlet(eat)

g2=greenlet(play)

g1.switch('egon')#可以在第一次switch时传入参数,以后都不需要

单纯的切换(在没有io的情况下或者没有重复开辟内存空间的操作),反而会降低程序的执行速度

greenlet只是提供了一种比generator更加便捷的切换方式,当切到一个任务执行时如果遇到io,那就原地阻塞,仍然是没有解决遇到IO自动切换来提升效率的问题。

单线程里的这20个任务的代码通常会既有计算操作又有阻塞操作,我们完全可以在执行任务1时遇到阻塞,就利用阻塞的时间去执行任务2。。。。如此,才能提高效率,这就用到了Gevent模块(后续)。

greenlet module与class

我们首先看一下greenlet这个module里面的属性

>> dir(greenlet)

['GREENLET_USE_GC', 'GREENLET_USE_TRACING', 'GreenletExit', '_C_API', '__doc__', '__file__', '__name__', '__package__', '__version__', 'error', 'getcurrent', 'gettrace', 'greenlet', 'settrace']

>>>

其中,比较重要的是getcurrent(), 类greenlet、异常类GreenletExit。

getcurrent()返回当前的greenlet实例;

GreenletExit:是一个特殊的异常,当触发了这个异常的时候,即使不处理,也不会抛到其parent(后面会提到协程中对返回值或者异常的处理)

然后我们再来看看greenlet.greenlet这个类:

>>> dir(greenlet.greenlet)

['GreenletExit', '__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__getstate__', '__hash__', '__init__', '__new__', '__nonzero__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__',   '__sizeof__', '__str__', '__subclasshook__', '_stack_saved', 'dead', 'error', 'getcurrent', 'gettrace', 'gr_frame', 'parent', 'run', 'settrace','switch', 'throw']

>>>

比较重要的几个属性:

run:当greenlet启动的时候会调用到这个callable,如果我们需要继承greenlet.greenlet时,需要重写该方法

switch:前面已经介绍过了,在greenlet之间切换

parent:可读写属性,后面介绍

dead:如果greenlet执行结束,那么该属性为true

throw:切换到指定greenlet后立即跑出异常

文章后面提到的greenlet大多都是指greenlet.greenlet这个class,请注意区别。

parent属性

每一个Greenlet都有一个parent,一个新的greenlet在哪里创生,当前环境的greenlet就是这个新greenlet的parent。所有的greenlet构成一棵树,其跟节点就是还没有手动创建greenlet时候的”main” greenlet(事实上,在首次import greenlet的时候实例化)。当一个协程 正常结束,执行流程回到其对应的parent;或者在一个协程中抛出未被捕获的异常,该异常也是传递到其parent。

import greenlet

def test1(x, y):

print id(greenlet.getcurrent()), id(greenlet.getcurrent().parent) # 40240272 40239952

z = gr2.switch(x+y)

print 'back z', z

def test2(u):

print id(greenlet.getcurrent()), id(greenlet.getcurrent().parent) # 40240352 40239952

return 'hehe'

gr1 = greenlet.greenlet(test1)

gr2 = greenlet.greenlet(test2)

print id(greenlet.getcurrent()), id(gr1), id(gr2) # 40239952, 40240272, 40240352

print gr1.switch("hello", " world"), 'back to main' # hehe back to main

上述例子可以看到,尽量是从test1所在的协程gr1 切换到了gr2,但gr2的parent还是’main’ greenlet,因为默认的parent取决于greenlet的创生环境。另外 在test2中return之后整个返回值返回到了其parent,而不是switch到该协程的地方(即不是test1),这个跟我们平时的函数调用不一样,记住“switch not call”。对于异常 也是展开至parent。

mport greenlet

def test1(x, y):

try:

z = gr2.switch(x+y)

except Exception:

print 'catch Exception in test1'

def test2(u):

assert False

gr1 = greenlet.greenlet(test1)

gr2 = greenlet.greenlet(test2)

try:

gr1.switch("hello", " world")

except:

print 'catch Exception in main'

输出为:

catch Exception in main

Greenlet生命周期

文章开始的地方提到第一个例子中的gr2其实并没有正常结束,我们可以借用greenlet.dead这个属性来查看:

from greenlet import greenlet

def test1():

gr2.switch(1)

print 'test1 finished'

def test2(x):

print 'test2 first', x

z = gr1.switch()

print 'test2 back', z

gr1 = greenlet(test1)

gr2 = greenlet(test2)

gr1.switch()

print 'gr1 is dead?: %s, gr2 is dead?: %s' % (gr1.dead, gr2.dead)

gr2.switch()

print 'gr1 is dead?: %s, gr2 is dead?: %s' % (gr1.dead, gr2.dead)

print gr2.switch(10)

输出:

test2 first 1

test1 finished

gr1 is dead?: True, gr2 is dead?: False

test2 back ()

gr1 is dead?: True, gr2 is dead?: True

10

从这个例子可以看出:

只有当协程对应的函数执行完毕,协程才会die,所以第一次Check的时候gr2并没有die,因为第9行切换出去了就没切回来。在main中再switch到gr2的时候, 执行后面的逻辑,gr2 die。

如果试图再次switch到一个已经是dead状态的greenlet会怎么样呢,事实上会切换到其parent greenlet。

Greenlet Traceing

Greenlet也提供了接口使得程序员可以监控greenlet的整个调度流程。主要是gettrace 和 settrace(callback)函数。下面看一个例子:

def test_greenlet_tracing():

def callback(event, args):

print event, 'from', id(args[0]), 'to', id(args[1])

def dummy():

g2.switch()

def dummyexception():

raise Exception('excep in coroutine')

main = greenlet.getcurrent()

g1 = greenlet.greenlet(dummy)

g2 = greenlet.greenlet(dummyexception)

print 'main id %s, gr1 id %s, gr2 id %s' % (id(main), id(g1), id(g2))

oldtrace = greenlet.settrace(callback)

try:

g1.switch()

except:

print 'Exception'

finally:

greenlet.settrace(oldtrace)

test_greenlet_tracing()

输出:

main id 40604416, gr1 id 40604736, gr2 id 40604816

switch from 40604416 to 40604736

switch from 40604736 to 40604816

throw from 40604816 to 40604416

Exception

其中callback函数event是switch或者throw之一,表明是正常调度还是异常跑出;args是二元组,表示是从协程args[0]切换到了协程args[1]。上面的输出展示了切换流程:从main到gr1,然后到gr2,最后回到main。

Greenlet内存泄漏问题

使用greenlet需要注意一下三点:

第一:greenlet创生之后,一定要结束,不能switch出去就不回来了,否则容易造成内存泄露。

第二:python中每个线程都有自己的main greenlet及其对应的sub-greenlet ,不能线程之间的greenlet是不能相互切换的。

第三:不能存在循环引用,这个是官方文档明确说明。

”Greenlets do not participate in garbage collection; cycles involving data that is present in a greenlet’s frames will not be detected. “

from greenlet import greenlet, GreenletExit

huge = []

def show_leak():

def test1():

gr2.switch()

def test2():

huge.extend([x* x for x in range(100)])

gr1.switch()

print 'finish switch del huge'

del huge[:]

gr1 = greenlet(test1)

gr2 = greenlet(test2)

gr1.switch()

gr1 = gr2 = None

print 'length of huge is zero ? %s' % len(huge)

if __name__ == '__main__':

show_leak()

# output: length of huge is zero ? 100

在test2函数中 第11行,我们将huge清空,然后再第16行将gr1、gr2的引用计数降到了0。但运行结果告诉我们,第11行并没有执行,所以如果一个协程没有正常结束是很危险的,往往不符合程序员的预期。greenlet提供了解决这个问题的办法,官网文档提到:如果一个greenlet实例的引用计数变成0,那么会在上次挂起的地方抛出GreenletExit异常,这就使得我们可以通过try … finally 处理资源泄露的情况。如下面的代码:

from greenlet import greenlet, GreenletExit

huge = []

def show_leak():

def test1():

gr2.switch()

def test2():

huge.extend([x* x for x in range(100)])

try:

gr1.switch()

finally:

print 'finish switch del huge'

del huge[:]

gr1 = greenlet(test1)

gr2 = greenlet(test2)

gr1.switch()

gr1 = gr2 = None

print 'length of huge is zero ? %s' % len(huge)

if __name__ == '__main__':

show_leak()

# output :

# finish switch del huge

# length of huge is zero ? 0

上述代码的switch流程:main greenlet –> gr1 –> gr2 –> gr1 –> main greenlet, 很明显gr2没有正常结束(在第10行刮起了)。第18行之后gr1,gr2的引用计数都变成0,那么会在第10行抛出GreenletExit异常,因此finally语句有机会执行。同时,在文章开始介绍Greenlet module的时候也提到了,GreenletExit这个异常并不会抛出到parent,所以main greenlet也不会出异常。

Gevent介绍

#安装

pip3 install gevent

Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程。 Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。

#用法

g1=gevent.spawn(func,1,,2,3,x=4,y=5)创建一个协程对象g1,spawn括号内第一个参数是函数名,如eat,后面可以有多个参数,可以是位置实参或关键字实参,都是传给函数eat的

g2=gevent.spawn(func2)

g1.join() #等待g1结束

g2.join() #等待g2结束

#或者上述两步合作一步:gevent.joinall([g1,g2])

g1.value#拿到func1的返回值

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

import gevent

def eat(name):

print('%s eat 1' %name)

gevent.sleep(2)

print('%s eat 2' %name)

def play(name):

print('%s play 1' %name)

gevent.sleep(1)

print('%s play 2' %name)

g1=gevent.spawn(eat,'egon')

g2=gevent.spawn(play,name='egon')

g1.join()

g2.join()

#或者gevent.joinall([g1,g2])

print('主')

上例gevent.sleep(2)模拟的是gevent可以识别的io阻塞,

而time.sleep(2)或其他的阻塞,gevent是不能直接识别的需要用下面一行代码,打补丁,就可以识别了

from gevent import monkey;monkey.patch_all()必须放到被打补丁者的前面,如time,socket模块之前

或者我们干脆记忆成:要用gevent,需要将from gevent import monkey;monkey.patch_all()放到文件的开头

from gevent import monkey;monkey.patch_all()

import gevent

import time

def eat():

print('eat food 1')

time.sleep(2)

print('eat food 2')

def play():

print('play 1')

time.sleep(1)

print('play 2')

g1=gevent.spawn(eat)

g2=gevent.spawn(play_phone)

gevent.joinall([g1,g2])

print('主')

我们可以用threading.current_thread().getName()来查看每个g1和g2,查看的结果为DummyThread-n,即假线程

Gevent之同步与异步

from gevent import spawn,joinall,monkey;monkey.patch_all()

import time

def task(pid):

"""

Some non-deterministic task

"""

time.sleep(0.5)

print('Task %s done' % pid)

def synchronous():

for i in range(10):

task(i)

def asynchronous():

g_l=[spawn(task,i) for i in range(10)]

joinall(g_l)

if __name__ == '__main__':

print('Synchronous:')

synchronous()

print('Asynchronous:')

asynchronous()

#上面程序的重要部分是将task函数封装到Greenlet内部线程的gevent.spawn。 初始化的greenlet列表存放在数组threads中,此数组被传给gevent.joinall 函数,后者阻塞当前流程,并执行所有给定的greenlet。执行流程只会在 所有greenlet执行完后才会继续向下走。

Gevent之应用举例一

from gevent import monkey;monkey.patch_all()

import gevent

import requests

import time

def get_page(url):

print('GET: %s' %url)

response=requests.get(url)

if response.status_code == 200:

print('%d bytes received from %s' %(len(response.text),url))

start_time=time.time()

gevent.joinall([

gevent.spawn(get_page,'https://www.python.org/'),

gevent.spawn(get_page,'https://www.yahoo.com/'),

gevent.spawn(get_page,'https://github.com/'),

])

stop_time=time.time()

print('run time is %s' %(stop_time-start_time))

协程应用:爬虫

Gevent之应用举例二

通过gevent实现单线程下的socket并发(from gevent import monkey;monkey.patch_all()一定要放到导入socket模块之前,否则gevent无法识别socket的阻塞)

服务端

from gevent import monkey;monkey.patch_all()

from socket import *

import gevent

#如果不想用money.patch_all()打补丁,可以用gevent自带的socket

# from gevent import socket

# s=socket.socket()

def server(server_ip,port):

s=socket(AF_INET,SOCK_STREAM)

s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)

s.bind((server_ip,port))

s.listen(5)

while True:

conn,addr=s.accept()

gevent.spawn(talk,conn,addr)

def talk(conn,addr):

try:

while True:

res=conn.recv(1024)

print('client %s:%s msg: %s' %(addr[0],addr[1],res))

conn.send(res.upper())

except Exception as e:

print(e)

finally:

conn.close()

if __name__ == '__main__':

server('127.0.0.1',8080)

客户端

from socket import *

client=socket(AF_INET,SOCK_STREAM)

client.connect(('127.0.0.1',8080))

while True:

msg=input('>>: ').strip()

if not msg:continue

client.send(msg.encode('utf-8'))

msg=client.recv(1024)

print(msg.decode('utf-8'))

多线程并发多个客户端

from threading import Thread

from socket import *

import threading

def client(server_ip,port):

c=socket(AF_INET,SOCK_STREAM) #套接字对象一定要加到函数内,即局部名称空间内,放在函数外则被所有线程共享,则大家公用一个套接字对象,那么客户端端口永远一样了

c.connect((server_ip,port))

count=0

while True:

c.send(('%s say hello %s' %(threading.current_thread().getName(),count)).encode('utf-8'))

msg=c.recv(1024)

print(msg.decode('utf-8'))

count+=1

if __name__ == '__main__':

for i in range(500):

t=Thread(target=client,args=('127.0.0.1',8080))

t.start()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/260177.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python相减函数subs,SUBS(subs是什么函数)

matlab中subs()是符号计算函数,详细用法可以在Matlab的Command Windows输入:help subs。subs()函数表示将符号表达式中的某些符号变量替换为指定的新的变.f1subs(f,t,t3); f2subs(f1,t,2*t); f3subs(f2,t,-t); subplot(2,2,1);ezplot(f,[-8,8]);。subs是…

hdu--1075--字典树||map

做这题的时候 我完全没想到 字典树 就直接用map来做了 - 我是有 多不 敏感啊~~ 然后去 discuss 一看 很多都是说 字典树的问题.... 字典树 给我感觉 它的各个操作的意思都很清晰明了 直接手写 不那么容易啊。。 晚些 时候 试下来写------用map写是真心方便 只要注意下那么\n的吸…

归档七

课后作业1 运行 TestInherits.java ,观察输出,总结父类与子类之间构造方法的调用关系修改Parent构造方法的代码,调用GrandParent的另一个构造函数。 class Grandparent { public Grandparent() { System.out.println("GrandParent Creat…

php的类装载的步骤,设计PHP自动类装载功能

在使用面向对象方法做PHP开发时,可能会经常使用到各个路径中的类文件,这就需要大量的 include 或 require,而 PHP 提供了一个比较快捷的方式,就是利用函数 __autoload 可以编程实现动态的类装载功能,这样就不需要手动的…

python 重定向 ctf_3.CTF——python利用工具

web AWD 攻与防CTF线下赛主要考察代码审计能力及运维能力,代码审计发现漏洞,python写利用漏洞,运维发现可疑攻击目标,异常流量,异常权限,重要业务备份与还原。用运维的知识加固系统与业务。当被人攻击以后&…

网站首页幻灯片

Js页面: View Code /** * 大眼睛广告轮播 */ var indexEye {autoTime: 0,init: function () {var eyeObj $("#dyj_pics a:eq(0) img:eq(0)");eyeObj.attr("src", eyeObj.attr("data-imgSrc"));eyeObj.load(function () {indexEye.autoTime se…

【java】错误 找不到或无法加载主类

很诡异,class文件夹下的class文件没有了,删除文件夹 ,重新编译下。。。转载于:https://www.cnblogs.com/merlini/p/3892719.html

Qt之QAbstractItemView视图项拖拽(二)

一、需求说明 上一篇文章Qt之QAbstractItemView视图项拖拽(一)讲述了实现QAbstractItemView视图项拖拽的一种方式,是基于QDrag实现的,这个类是qt自己封装好了的,所以可定制性也就没有了那么强,最明显的是,这个类在执…

电脑控制苹果手机_必备神器,电脑控制手机

序一款电脑端的神器,它可以任意的操纵你的手机。****QtScrcpy可以通过USB(或通过TCP/IP)连接Android设备,并进行显示和控制。不需要root权限。单个应用程序最多支持16个安卓设备同时连接。同时支持GNU/Linux,Windows和MacOS三大主流桌面平台。…

php未定义要怎样做,php-Behat-未定义的功能步骤

我设置了一个简单的测试场景来学习behat,但是我遇到了一些问题.我正在关注THIS教程.这是我的专题节目:Feature: showThis is a behat feature to test the article pages.##TODOScenario: I want to view a detailed article pageGiven I am logged inAnd Im on &qu…

CentOS 命令大全 (转)

1、查看系统使用端口并释放端口 [rootmy_nn_01 WEB-INF]# lsof -w -n -i tcp:80 COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME java 24065 root 34u IPv6 269149 TCP *:http (LISTEN) [rootmy_nn_01 WEB-INF]# kill -9 24065 2、以KB/MB形式显示文件列表…

微信接口改良

之前公司微信开发的时候 写了个微信的接口改良版,当然好多想改进的都没改。。大概是太懒了 (囧 /*** Created by DFH on 13-12-16.*//*--htmlvar shareData {//分享展示图片地址 **必须"imgUrl": "a.jpg",//分享至朋友圈链接 **必须&q…

生活大爆炸版石头剪刀布

题目描述 Description石头剪刀布是常见的猜拳游戏:石头胜剪刀,剪刀胜布,布胜石头。如果两个人出拳一样,则不分胜负。在《生活大爆炸》第二季第8集中出现了一种石头剪刀布的升级版游戏。升级版游戏在传统的石头剪刀布游戏的基础上&…

oracle18c卸载方法,在debian 10上安装和卸载oracle数据库快捷版18c第4版

安装oracle-xe-18c的步骤此安装向导依赖软件包alien。由于oracle并未提供oracle-xe-18c的deb包,故需要通过alien命令将oracle-xe-18c的rpm格式的安装包导出新的deb格式的安装包:sudo alien --scripts -d oracle-database-xe-18c-1.0-1.x86_64.rpm相应rpm…

解决:缺少aclocal、autoconf、automake

下载三个包:autoconf-2.68.tar.bz2、automake-1.11.1.tar.bz2、m4-1.4.14.tar.bz2 1、su - root 2、tar xjf XXXXX.tar.bz2 3、cd m4/ 4、./configure make make install 5、cd autoconf/ 6、./configure make make install 7、cd automake/ 8、./configure…

jquery事件 on(),live(),delegate(),blind()

jQuery推出on()的目的有2个,一是为了统一接口,二是为了提高性能, 所以从现在开始用on()替换bind(), live(), delegate吧。 尤其是不要再用live()了,因为它已经处于不推荐使用列表了[1.7已经被删除]。 如果只绑定一次事件&#xff…

Swift 开发的工具类,主要是提供正则表达式及其它,Github会长期维护

直接访问 GitHub 看代码 YYGRegular 我是: 语歌复制代码It is a regular expression used on iOS, which implement by Swift 这是一个基于swift快捷开发的扩展类,目前的涵盖内容包括详细打印,正则表达式,会经常维护 介于是增加更…

用python庆祝生日_生日到底该过阴历还是阳历好呢?不是迷信,都怪我们大意!...

过生日到底该过阴历还是阳历?答案说出来你可能都不信在我们国家,过生日有两种不同的方式,因为有两种不同的日子的计算方式,分为阴历和阳历。一般来说,在农村和一些比较落后的地方,人们习惯于用阴历来计算生…

websphere jndi oracle,websphere7.0获得JNDI连接报invalid username/password

Exception in thread "P497968:O0:CT" java.sql.SQLException: ORA-01017: invalid username/password; logon deniedDSRA0010E: SQL 状态:72000,错误码:1,017at oracle.jdbc.driver.SQLStateMapping.newSQLException(SQLStateMapp…

WSS3.0自带数据库可以使用SQL 2005 Server Management Studio来管理

默认情况下,安装完WSS3.0后,会自动安装一个自带的SQL Server 2005 Embedded Edition数据库,但是此数据库却没有管理工具,不像安装SQL 2005其它版本会有管理工具。如果你要管理数据库,这时怎么办呢。经过俺试了一上午了&#xff0c…