Python3标准库之threading进程中管理并发操作方法


Posted in Python onMarch 30, 2020

1. threading进程中管理并发操作

threading模块提供了管理多个线程执行的API,允许程序在同一个进程空间并发的运行多个操作。

1.1 Thread对象

要使用Thread,最简单的方法就是用一个目标函数实例化一个Thread对象,并调用start()让它开始工作。

import threading
def worker():
 """thread worker function"""
 print('Worker')
threads = []
for i in range(5):
 t = threading.Thread(target=worker)
 threads.append(t)
 t.start()

输出有5行,每一行都是"Worker"。

Python3标准库之threading进程中管理并发操作方法

如果能够创建一个线程,并向它传递参数告诉它要完成什么工作,那么这会很有用。任何类型的对象都可以作为参数传递到线程。下面的例子传递了一个数,线程将打印出这个数。

import threading
def worker(num):
 """thread worker function"""
 print('Worker: %s' % num)
threads = []
for i in range(5):
 t = threading.Thread(target=worker, args=(i,))
 threads.append(t)
 t.start()

现在这个整数参数会包含在各线程打印的消息中。

Python3标准库之threading进程中管理并发操作方法

1.2 确定当前线程

使用参数来标识或命名线程很麻烦,也没有必要。每个Thread实例都有一个带有默认值的名,该默认值可以在创建线程时改变。如果服务器进程中有多个服务线程处理不同的操作,那么在这样的服务器进程中,对线程命名就很有用。

import threading
import time
def worker():
 print(threading.current_thread().getName(), 'Starting')
 time.sleep(0.2)
 print(threading.current_thread().getName(), 'Exiting')
def my_service():
 print(threading.current_thread().getName(), 'Starting')
 time.sleep(0.3)
 print(threading.current_thread().getName(), 'Exiting')
t = threading.Thread(name='my_service', target=my_service)
w = threading.Thread(name='worker', target=worker)
w2 = threading.Thread(target=worker) # use default name
w.start()
w2.start()
t.start()

调试输出的每一行中包含有当前线程的名。线程名列中有"Thread-1"的行对应未命名的线程w2。

Python3标准库之threading进程中管理并发操作方法

大多数程序并不使用print来进行调试。logging模块支持将线程名嵌入到各个日志消息中(使用格式化代码%(threadName)s)。通过把线程名包含在日志消息中,就能跟踪这些消息的来源。

import logging
import threading
import time
def worker():
 logging.debug('Starting')
 time.sleep(0.2)
 logging.debug('Exiting')
def my_service():
 logging.debug('Starting')
 time.sleep(0.3)
 logging.debug('Exiting')
logging.basicConfig(
 level=logging.DEBUG,
 format='[%(levelname)s] (%(threadName)-10s) %(message)s',
)
t = threading.Thread(name='my_service', target=my_service)
w = threading.Thread(name='worker', target=worker)
w2 = threading.Thread(target=worker) # use default name
w.start()
w2.start()
t.start()

而且logging是线程安全的,所以来自不同线程的消息在输出中会有所区分。

Python3标准库之threading进程中管理并发操作方法

1.3 守护与非守护线程

到目前为止,示例程序都在隐式地等待所有线程完成工作之后才退出。不过,程序有时会创建一个线程作为守护线程(daemon),这个线程可以一直运行而不阻塞主程序退出。

如果一个服务不能很容易地中断线程,或者即使让线程工作到一半时中止也不会造成数据损失或破坏(例如,为一个服务监控工具生成“心跳”的线程),那么对于这些服务,使用守护线程就很有用。要标志一个线程为守护线程,构造线程时便要传入daemon=True或者要调用它的setDaemon()方法并提供参数True。默认情况下线程不作为守护线程。

import threading
import time
import logging
def daemon():
 logging.debug('Starting')
 time.sleep(0.2)
 logging.debug('Exiting')
def non_daemon():
 logging.debug('Starting')
 logging.debug('Exiting')
logging.basicConfig(
 level=logging.DEBUG,
 format='(%(threadName)-10s) %(message)s',
)
d = threading.Thread(name='daemon', target=daemon, daemon=True)
t = threading.Thread(name='non-daemon', target=non_daemon)
d.start()
t.start()

这个代码的输出中不包含守护线程的“Exiting“消息,因为在从sleep()调用唤醒守护线程之前,所有非守护线程(包括主线程)已经退出。

Python3标准库之threading进程中管理并发操作方法

要等待一个守护线程完成工作,需要使用join()方法。

import threading
import time
import logging
def daemon():
 logging.debug('Starting')
 time.sleep(0.2)
 logging.debug('Exiting')
def non_daemon():
 logging.debug('Starting')
 logging.debug('Exiting')
logging.basicConfig(
 level=logging.DEBUG,
 format='(%(threadName)-10s) %(message)s',
)
d = threading.Thread(name='daemon', target=daemon, daemon=True)
t = threading.Thread(name='non-daemon', target=non_daemon)
d.start()
t.start()
d.join()
t.join()

使用join()等待守护线程退出意味着它有机会生成它的"Exiting"消息。

Python3标准库之threading进程中管理并发操作方法

默认地,join()会无限阻塞。或者,还可以传入一个浮点值,表示等待线程在多长时间(秒数)后变为不活动。即使线程在这个时间段内未完成,join()也会返回。

import threading
import time
import logging
def daemon():
 logging.debug('Starting')
 time.sleep(0.2)
 logging.debug('Exiting')
def non_daemon():
 logging.debug('Starting')
 logging.debug('Exiting')
logging.basicConfig(
 level=logging.DEBUG,
 format='(%(threadName)-10s) %(message)s',
)
d = threading.Thread(name='daemon', target=daemon, daemon=True)
t = threading.Thread(name='non-daemon', target=non_daemon)
d.start()
t.start()
d.join(0.1)
print('d.isAlive()', d.isAlive())
t.join()

由于传人的超时时间小于守护线程睡眠的时间,所以join()返回之后这个线程仍是"活着"。

Python3标准库之threading进程中管理并发操作方法

1.4 枚举所有线程

没有必要为所有守护线程维护一个显示句柄来确保它们在退出主进程之前已经完成。

enumerate()会返回活动 Thread实例的一个列表。这个列表也包括当前线程,由于等待当前线程终止(join)会引入一种死锁情况,所以必须跳过。

import random
import threading
import time
import logging
def worker():
 """thread worker function"""
 pause = random.randint(1, 5) / 10
 logging.debug('sleeping %0.2f', pause)
 time.sleep(pause)
 logging.debug('ending')
logging.basicConfig(
 level=logging.DEBUG,
 format='(%(threadName)-10s) %(message)s',
)
for i in range(3):
 t = threading.Thread(target=worker, daemon=True)
 t.start()
main_thread = threading.main_thread()
for t in threading.enumerate():
 if t is main_thread:
 continue
 logging.debug('joining %s', t.getName())
 t.join()

由于工作线程睡眠的时间量是随机的,所以这个程序的输出可能有变化。

Python3标准库之threading进程中管理并发操作方法

1.5 派生线程

开始时,Thread要完成一些基本初始化,然后调用其run()方法,这会调用传递到构造函数的目标函数。要创建Thread的一个子类,需要覆盖run()来完成所需的工作。

import threading
import logging
class MyThread(threading.Thread):
 def run(self):
 logging.debug('running')
logging.basicConfig(
 level=logging.DEBUG,
 format='(%(threadName)-10s) %(message)s',
)
for i in range(5):
 t = MyThread()
 t.start()

run()的返回值将被忽略。

Python3标准库之threading进程中管理并发操作方法

由于传递到Thread构造函数的args和kwargs值保存在私有变量中(这些变量名都有前缀),所以不能很容易地从子类访问这些值。要向一个定制的线程类型传递参数,需要重新定义构造函数,将这些值保存在子类可见的一个实例属性中。

import threading
import logging
class MyThreadWithArgs(threading.Thread):
 def __init__(self, group=None, target=None, name=None,
  args=(), kwargs=None, *, daemon=None):
 super().__init__(group=group, target=target, name=name,
  daemon=daemon)
 self.args = args
 self.kwargs = kwargs
 def run(self):
 logging.debug('running with %s and %s',
  self.args, self.kwargs)
logging.basicConfig(
 level=logging.DEBUG,
 format='(%(threadName)-10s) %(message)s',
)
for i in range(5):
 t = MyThreadWithArgs(args=(i,), kwargs={'a': 'A', 'b': 'B'})
 t.start()

MyThreadwithArgs使用的API与Thread相同,不过类似于其他定制类,这个类可以轻松地修改构造函数方法,以取得更多参数或者与线程用途更直接相关的不同参数。

Python3标准库之threading进程中管理并发操作方法

1.6 定时器线程

有时出于某种原因需要派生Thread,Timer就是这样一个例子,Timer也包含在threading中。Timer在一个延迟之后开始工作,而且可以在这个延迟期间内的任意时刻被取消。

import threading
import time
import logging
def delayed():
 logging.debug('worker running')
logging.basicConfig(
 level=logging.DEBUG,
 format='(%(threadName)-10s) %(message)s',
)
t1 = threading.Timer(0.3, delayed)
t1.setName('t1')
t2 = threading.Timer(0.3, delayed)
t2.setName('t2')
logging.debug('starting timers')
t1.start()
t2.start()
logging.debug('waiting before canceling %s', t2.getName())
time.sleep(0.2)
logging.debug('canceling %s', t2.getName())
t2.cancel()
logging.debug('done')

这个例子中,第二个定时器永远不会运行,看起来第一个定时器在主程序的其余部分完成之后还会运行。由于这不是一个守护线程,所以在主线程完成时其会隐式退出。

Python3标准库之threading进程中管理并发操作方法

1.7 线程间传送信号

尽管使用多线程的目的是并发地运行单独的操作,但有时也需要在两个或多个线程中同步操作。事件对象是实现线程间安全通信的一种简单方法。Event管理一个内部标志,调用者可以用set()和clear()方法控制这个标志。其他线程可以使用wait()暂停,直到这个标志被设置,可有效地阻塞进程直至允许这些线程继续。

import logging
import threading
import time
def wait_for_event(e):
 """Wait for the event to be set before doing anything"""
 logging.debug('wait_for_event starting')
 event_is_set = e.wait()
 logging.debug('event set: %s', event_is_set)
def wait_for_event_timeout(e, t):
 """Wait t seconds and then timeout"""
 while not e.is_set():
 logging.debug('wait_for_event_timeout starting')
 event_is_set = e.wait(t)
 logging.debug('event set: %s', event_is_set)
 if event_is_set:
 logging.debug('processing event')
 else:
 logging.debug('doing other work')
logging.basicConfig(
 level=logging.DEBUG,
 format='(%(threadName)-10s) %(message)s',
)
e = threading.Event()
t1 = threading.Thread(
 name='block',
 target=wait_for_event,
 args=(e,),
)
t1.start()
t2 = threading.Thread(
 name='nonblock',
 target=wait_for_event_timeout,
 args=(e, 2),
)
t2.start()
logging.debug('Waiting before calling Event.set()')
time.sleep(0.3)
e.set()
logging.debug('Event is set')

wait()方法取一个参数,表示等待事件的时间(秒数),达到这个时间后就超时。它会返回一个布尔值,指示事件是否已设置,使调用者知道wait()为什么返回。可以对事件单独地使用is_set()方法而不必担心阻塞。

在这个例子中,wait_for_event_timeout()将检查事件状态而不会无限阻塞。wait_for_event()在wait()调用的位置阻塞,事件状态改变之前它不会返回。

Python3标准库之threading进程中管理并发操作方法

1.8 控制资源访问

除了同步线程操作,还有一点很重要,要能够控制对共享资源的访问,从而避免破坏或丢失数据。Python的内置数据结构(列表、字典等)是线程安全的,这是Python使用原子字节码来管理这些数据结构的一个副作用(更新过程中不会释放保护Python内部数据结构的全局解释器锁GIL(Global Interpreter Lock))。Python中实现的其他数据结构或更简单的类型(如整数和浮点数)则没有这个保护。要保证同时安全地访问一个对象,可以使用一个Lock对象。

import logging
import random
import threading
import time
class Counter:
 def __init__(self, start=0):
 self.lock = threading.Lock()
 self.value = start
 def increment(self):
 logging.debug('Waiting for lock')
 self.lock.acquire()
 try:
 logging.debug('Acquired lock')
 self.value = self.value + 1
 finally:
 self.lock.release()
def worker(c):
 for i in range(2):
 pause = random.random()
 logging.debug('Sleeping %0.02f', pause)
 time.sleep(pause)
 c.increment()
 logging.debug('Done')
logging.basicConfig(
 level=logging.DEBUG,
 format='(%(threadName)-10s) %(message)s',
)
counter = Counter()
for i in range(2):
 t = threading.Thread(target=worker, args=(counter,))
 t.start()
logging.debug('Waiting for worker threads')
main_thread = threading.main_thread()
for t in threading.enumerate():
 if t is not main_thread:
 t.join()
logging.debug('Counter: %d', counter.value)

在这个例子中,worker()函数使一个Counter实例递增,这个实例管理着一个Lock,以避免两个线程同时改变其内部状态。如果没有使用Lock,就有可能丢失一次对value属性的修改。

Python3标准库之threading进程中管理并发操作方法

要确定是否有另一个线程请求这个锁而不影响当前线程,可以向acquire()的blocking参数传入False。在下一个例子中,worker()想要分别得到3次锁,并统计为得到锁而尝试的次数。与此同时,lock_holder()在占有和释放锁之间循环,每个状态会短暂暂停,以模拟负载情况。

import logging
import threading
import time
def lock_holder(lock):
 logging.debug('Starting')
 while True:
 lock.acquire()
 try:
 logging.debug('Holding')
 time.sleep(0.5)
 finally:
 logging.debug('Not holding')
 lock.release()
 time.sleep(0.5)
def worker(lock):
 logging.debug('Starting')
 num_tries = 0
 num_acquires = 0
 while num_acquires < 3:
 time.sleep(0.5)
 logging.debug('Trying to acquire')
 have_it = lock.acquire(0)
 try:
 num_tries += 1
 if have_it:
 logging.debug('Iteration %d: Acquired',
  num_tries)
 num_acquires += 1
 else:
 logging.debug('Iteration %d: Not acquired',
  num_tries)
 finally:
 if have_it:
 lock.release()
 logging.debug('Done after %d iterations', num_tries)
logging.basicConfig(
 level=logging.DEBUG,
 format='(%(threadName)-10s) %(message)s',
)
lock = threading.Lock()
holder = threading.Thread(
 target=lock_holder,
 args=(lock,),
 name='LockHolder',
 daemon=True,
)
holder.start()
worker = threading.Thread(
 target=worker,
 args=(lock,),
 name='Worker',
)
worker.start()

worker()需要超过3次迭代才能得到3次锁。

Python3标准库之threading进程中管理并发操作方法

1.8.1 再入锁

正常的Lock对象不能请求多次,即使是由同一个线程请求也不例外。如果同一个调用链中的多个函数访问一个锁,则可能会产生我们不希望的副作用。

import threading
lock = threading.Lock()
print('First try :', lock.acquire())
print('Second try:', lock.acquire(0))

在这里,对第二个acquire()调用给定超时值为0,以避免阻塞,因为锁已经被第一个调用获得。

Python3标准库之threading进程中管理并发操作方法

如果同一个线程的不同代码需要"重新获得"锁,那么在这种情况下要使用RLock。

import threading
lock = threading.RLock()
print('First try :', lock.acquire())
print('Second try:', lock.acquire(0))

与前面的例子相比,对代码唯一的修改就是用RLock替换Lock。

Python3标准库之threading进程中管理并发操作方法

1.8.2 锁作为上下文管理器

锁实现了上下文管理器API,并与with语句兼容。使用with则不再需要显式地获得和释放锁。

import threading
import logging
def worker_with(lock):
 with lock:
 logging.debug('Lock acquired via with')
def worker_no_with(lock):
 lock.acquire()
 try:
 logging.debug('Lock acquired directly')
 finally:
 lock.release()
logging.basicConfig(
 level=logging.DEBUG,
 format='(%(threadName)-10s) %(message)s',
)
lock = threading.Lock()
w = threading.Thread(target=worker_with, args=(lock,))
nw = threading.Thread(target=worker_no_with, args=(lock,))
w.start()
nw.start()

函数worker_with()worker_no_with()用等价的方式管理锁。

Python3标准库之threading进程中管理并发操作方法

1.9 同步线程

除了使用Event,还可以通过使用一个Condition对象来同步线程。由于Condition使用了一个Lock,所以它可以绑定到一个共享资源,允许多个线程等待资源更新。在下一个例子中,consumer()线程要等待设置了Condition才能继续。producer()线程负责设置条件,以及通知其他线程继续。

import logging
import threading
import time
def consumer(cond):
 """wait for the condition and use the resource"""
 logging.debug('Starting consumer thread')
 with cond:
 cond.wait()
 logging.debug('Resource is available to consumer')
def producer(cond):
 """set up the resource to be used by the consumer"""
 logging.debug('Starting producer thread')
 with cond:
 logging.debug('Making resource available')
 cond.notifyAll()
logging.basicConfig(
 level=logging.DEBUG,
 format='%(asctime)s (%(threadName)-2s) %(message)s',
)
condition = threading.Condition()
c1 = threading.Thread(name='c1', target=consumer,
  args=(condition,))
c2 = threading.Thread(name='c2', target=consumer,
  args=(condition,))
p = threading.Thread(name='p', target=producer,
  args=(condition,))
c1.start()
time.sleep(0.2)
c2.start()
time.sleep(0.2)
p.start()

这些线程使用with来获得与Condition关联的锁。也可以显式地使用acquire()和release()方法。

Python3标准库之threading进程中管理并发操作方法

屏障(barrier)是另一种线程同步机制。Barrier会建立一个控制点,所有参与线程会在这里阻塞,直到所有这些参与“方”都到达这一点。采用这种方法,线程可以单独启动然后暂停,直到所有线程都准备好才可以继续。

import threading
import time
def worker(barrier):
 print(threading.current_thread().name,
 'waiting for barrier with {} others'.format(
 barrier.n_waiting))
 worker_id = barrier.wait()
 print(threading.current_thread().name, 'after barrier',
 worker_id)
NUM_THREADS = 3
barrier = threading.Barrier(NUM_THREADS)
threads = [
 threading.Thread(
 name='worker-%s' % i,
 target=worker,
 args=(barrier,),
 )
 for i in range(NUM_THREADS)
]
for t in threads:
 print(t.name, 'starting')
 t.start()
 time.sleep(0.1)
for t in threads:
 t.join()

在这个例子中,Barrier被配置为会阻塞线程,直到3个线程都在等待。满足这个条件时,所有线程被同时释放从而越过这个控制点。wait()的返回值指示了释放的参与线程数,可以用来限制一些线程做清理资源等动作。

Python3标准库之threading进程中管理并发操作方法

Barrier的abort()方法会使所有等待线程接收一个BrokenBarrierError。如果线程在wait()上被阻塞而停止处理,这就允许线程完成清理工作。

import threading
import time
def worker(barrier):
 print(threading.current_thread().name,
 'waiting for barrier with {} others'.format(
 barrier.n_waiting))
 try:
 worker_id = barrier.wait()
 except threading.BrokenBarrierError:
 print(threading.current_thread().name, 'aborting')
 else:
 print(threading.current_thread().name, 'after barrier',
 worker_id)
NUM_THREADS = 3
barrier = threading.Barrier(NUM_THREADS + 1)
threads = [
 threading.Thread(
 name='worker-%s' % i,
 target=worker,
 args=(barrier,),
 )
 for i in range(NUM_THREADS)
]
for t in threads:
 print(t.name, 'starting')
 t.start()
 time.sleep(0.1)
barrier.abort()
for t in threads:
 t.join()

这个例子将Barrier配置为多加一个线程,即需要比实际启动的线程再多一个参与线程,所以所有线程中的处理都会阻塞。在被阻塞的各个线程中,abort()调用会产生一个异常。

Python3标准库之threading进程中管理并发操作方法

1.10 限制资源的并发访问

有时可能需要允许多个工作线程同时访问一个资源,但要限制总数。例如,连接池支持同时连接,但数目可能是固定的,或者一个网络应用可能支持固定数目的并发下载。这些连接就可以使用Semaphore来管理。

import logging
import threading
import time
class ActivePool:
 def __init__(self):
 super(ActivePool, self).__init__()
 self.active = []
 self.lock = threading.Lock()
 def makeActive(self, name):
 with self.lock:
 self.active.append(name)
 logging.debug('Running: %s', self.active)
 def makeInactive(self, name):
 with self.lock:
 self.active.remove(name)
 logging.debug('Running: %s', self.active)
def worker(s, pool):
 logging.debug('Waiting to join the pool')
 with s:
 name = threading.current_thread().getName()
 pool.makeActive(name)
 time.sleep(0.1)
 pool.makeInactive(name)
logging.basicConfig(
 level=logging.DEBUG,
 format='%(asctime)s (%(threadName)-2s) %(message)s',
)
pool = ActivePool()
s = threading.Semaphore(2)
for i in range(4):
 t = threading.Thread(
 target=worker,
 name=str(i),
 args=(s, pool),
 )
 t.start()

在这个例子中,ActivePool类只作为一种便利方法,用来跟踪某个给定时刻哪些线程能够运行。真正的资源池会为新的活动线程分配一个连接或另外某个值,并且当这个线程工作完成时再回收这个值。在这里,资源池只是用来保存活动线程的名,以显示至少有两个线程在并发运行。

Python3标准库之threading进程中管理并发操作方法

1.11 线程特定的数据

有些资源需要锁定以便多个线程使用,另外一些资源则需要保护,以使它们对并非是这些资源的“所有者”的线程隐藏。local()函数会创建一个对象,它能够隐藏值,使其在不同线程中无法被看到。

import random
import threading
import logging
def show_value(data):
 try:
 val = data.value
 except AttributeError:
 logging.debug('No value yet')
 else:
 logging.debug('value=%s', val)
def worker(data):
 show_value(data)
 data.value = random.randint(1, 100)
 show_value(data)
logging.basicConfig(
 level=logging.DEBUG,
 format='(%(threadName)-10s) %(message)s',
)
local_data = threading.local()
show_value(local_data)
local_data.value = 1000
show_value(local_data)
for i in range(2):
 t = threading.Thread(target=worker, args=(local_data,))
 t.start()

属性local_data.value对所有线程都不可见,除非在某个线程中设置了这个属性,这个线程才能看到它。

Python3标准库之threading进程中管理并发操作方法

要初始化设置以使所有线程在开始时都有相同的值,可以使用一个子类,并在_init_()中设置这些属性。

import random
import threading
import logging
def show_value(data):
 try:
 val = data.value
 except AttributeError:
 logging.debug('No value yet')
 else:
 logging.debug('value=%s', val)
def worker(data):
 show_value(data)
 data.value = random.randint(1, 100)
 show_value(data)
class MyLocal(threading.local):
 def __init__(self, value):
 super().__init__()
 logging.debug('Initializing %r', self)
 self.value = value
logging.basicConfig(
 level=logging.DEBUG,
 format='(%(threadName)-10s) %(message)s',
)
local_data = MyLocal(1000)
show_value(local_data)
for i in range(2):
 t = threading.Thread(target=worker, args=(local_data,))
 t.start()

这会在相同的对象上调用_init_()(注意id()值),每个线程中调用一次以设置默认值。

Python3标准库之threading进程中管理并发操作方法

总结

到此这篇关于Python3标准库:threading进程中管理并发操作的文章就介绍到这了,更多相关Python3标准库:threading进程中管理并发操作内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Python 相关文章推荐
Python实现把utf-8格式的文件转换成gbk格式的文件
Jan 22 Python
python爬虫 使用真实浏览器打开网页的两种方法总结
Apr 21 Python
Python针对给定列表中元素进行翻转操作的方法分析
Apr 27 Python
PyTorch CNN实战之MNIST手写数字识别示例
May 29 Python
Linux下python3.6.1环境配置教程
Sep 26 Python
对python3 Serial 串口助手的接收读取数据方法详解
Jun 12 Python
Python 中PyQt5 点击主窗口弹出另一个窗口的实现方法
Jul 04 Python
程序员的七夕用30行代码让Python化身表白神器
Aug 07 Python
Python随机函数库random的使用方法详解
Aug 21 Python
Python3读写Excel文件(使用xlrd,xlsxwriter,openpyxl3种方式读写实例与优劣)
Feb 13 Python
关于python3.7安装matplotlib始终无法成功的问题的解决
Jul 28 Python
python uuid生成唯一id或str的最简单案例
Jan 13 Python
解决django xadmin主题不显示和只显示bootstrap2的问题
Mar 30 #Python
Python2 与Python3的版本区别实例分析
Mar 30 #Python
django xadmin中form_layout添加字段显示方式
Mar 30 #Python
基于virtualenv创建python虚拟环境过程图解
Mar 30 #Python
Python实现RabbitMQ6种消息模型的示例代码
Mar 30 #Python
Anconda环境下Vscode安装Python的方法详解
Mar 29 #Python
配置python的编程环境之Anaconda + VSCode的教程
Mar 29 #Python
You might like
require(),include(),require_once()和include_once()区别
2008/03/27 PHP
php自动注册登录验证机制实现代码
2011/12/20 PHP
php实现生成验证码实例分享
2016/04/10 PHP
Yii2中cookie用法示例分析
2016/07/18 PHP
php源码 fsockopen获取网页内容实例详解
2016/09/24 PHP
php连接sftp的作用以及实例代码
2019/09/23 PHP
php数值计算num类简单操作示例
2020/05/15 PHP
js计数器代码
2006/11/04 Javascript
服务器端的JavaScript脚本 Node.js 使用入门
2012/03/07 Javascript
jquery中的$(document).ready()使用小结
2014/02/14 Javascript
JS 对象属性相关(检查属性、枚举属性等)
2015/04/05 Javascript
jQuery-1.9.1源码分析系列(十)事件系统之事件包装
2015/11/20 Javascript
JavaScript程序中实现继承特性的方式总结
2016/06/24 Javascript
使用ajaxfileupload.js实现上传文件功能
2016/08/13 Javascript
Bootstrap表单使用方法详解
2017/02/17 Javascript
BootStrap模态框和select2合用时input无法获取焦点的解决方法
2017/09/01 Javascript
利用vue和element-ui设置表格内容分页的实例
2018/03/02 Javascript
vscode中vue-cli项目es-lint的配置方法
2018/07/30 Javascript
vuejs+element UI点击编辑表格某一行时获取内容填入表单的示例
2018/10/31 Javascript
js验证身份证号码记录的方法
2019/04/26 Javascript
javascript写一个ajax自动拦截并下载数据代码实例
2019/09/07 Javascript
微信小程序点击按钮动态切换input的disabled禁用/启用状态功能
2020/03/07 Javascript
vue自定义标签和单页面多路由的实现代码
2020/05/03 Javascript
浅谈Vue使用Elementui修改默认的最快方法
2020/12/05 Vue.js
Python中的条件判断语句与循环语句用法小结
2016/03/21 Python
Python线程创建和终止实例代码
2018/01/20 Python
python3使用SMTP发送简单文本邮件
2018/06/19 Python
Python IDE Pycharm中的快捷键列表用法
2019/08/08 Python
python3中rank函数的用法
2019/11/27 Python
Python Pandas数据分析工具用法实例
2020/11/05 Python
HTML5 Canvas 起步(2) - 路径
2009/05/12 HTML / CSS
ORACLE第二个十问
2013/12/14 面试题
CSS实现fullpage.js全屏滚动效果的示例代码
2021/03/24 HTML / CSS
幼儿园教师节演讲稿
2014/09/03 职场文书
一个成功的互联网创业项目,必须满足这些要求
2019/08/23 职场文书
浅谈自定义校验注解ConstraintValidator
2021/06/30 Java/Android