基础补充
MIT CS6.001最后三节课
编写递归的三部曲:
- 编写基本条件,即递归式最终停止递归的条件
- 编写递归调用部分:即确定递归调用的条件
- 递归调用式(例如:factorial(n-1)),每层的递归调用的返回值我应该做什么操作。
在递归和迭代中代码块的时间复杂度计算,是输入大小和查找递归调用次数的乘积。[详细看算法设计笔记]
Python UCB CS61A
继承
class 类名(父类名):
代码块
在函数中super是调用self参数对象。
在python中一个类可继承多个基类。
try except
用于捕获异常,并处理
assert(断言
调试异常时,会在该处停止
装饰器
顾名思义,也就是在该函数的基础上,额外增加功能,但不修改原函数。其增加的部分再装饰器函数中,实际调用该函数时,试运行的装饰器代码块,并最后将执行结果
def my_decorator(func):
def wrapper():
print("Before function is called.")
func()
print("After function is called.")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
say_hello()
生成器
yield
关键字是 Python 中用于定义生成器函数的一部分。它允许函数在每次生成值时暂停执行,并将值返回给调用者。生成器函数的状态会被保存,以便在下一次调用时从上次停止的地方继续执行。yield
实际上是将值传递给生成器的迭代器。
当生成器函数执行到 yield
语句时,它将生成值并将其传递给迭代器,然后暂停函数的执行。当您调用生成器的 next()
方法或使用 for
循环迭代生成器时,生成器函数将从上次停止的地方继续执行,并在下一个 yield
处再次生成值。
这样,通过 yield
,您可以将值从生成器函数传递给调用者,而不必一次性生成所有值。这使得生成器适用于处理大型数据集或需要懒惰计算的情况。
示例:
def fact():
for x in range(5):
yield x
a = fact()
for i in a:
print(i)
线程和进程(包含同步和消息队列等)
进程是最基本的资源单位,线程是最小的执行单位。
线程
Thread构造函数创建一个新线程。而target指定线程运行的目标函数,args指定目标函数所需参数。
start表示准备好运行。current_thread()该函数将返回当前执行线程关联的对象。
import threading
def thread_example():
other = threading.Thread(target=thread_example,args=())
other.start
thread_example()
def example_excute():
print("WoW,i'm running:",threading.current_thread().native_id)
example_excute()
进程
python中的multiprocessing模块用于创建和同步进程类,但这些进程无法直接共享数据,而使用管道进行间接通信和数据共享。
import multiprocessing
def process_example():
other = multiprocessing.Process(target=process_example,args=())
other.start
process_example
def process_example_hello():
print(f"it's my:{multiprocessing.current_process().name}")
process_example_hello()
multiprocessing
模块为进程提供其他同步机制,包括同步队列、锁以及 Python 3.3 中的屏障。例如,可以使用锁或屏障将打印同步到屏幕,避免我们之前看到的不正确的显示输出。
并行之间引起问题
竞争条件
当两个不同的进程或线程对同一资源进行操作时,数据可能会被一个线程更改而另一个线程访问它,这就称为竞争条件。
import threading
from time import sleep
counter = [0]
def increment():
count = counter[0]
sleep(0) # try to force a switch to the other thread
counter[0] = count + 1
other = threading.Thread(target=increment, args=())
other.start()
increment()
print('count is now: ', counter[0])
并行问题的解决方案
利用标志变量解决方法
使用一个flag进行控制,生产者线程需要先把数据添加到list后,修改flag,告知消费者线程可进行
items = []
flag = []
def consume():
while not flag:
pass
print('items is', items)
def produce():
consumer = threading.Thread(target=consume, args=())
consumer.start()
for i in range(10):
items.append(i)
flag.append('go')
produce()
利用队列进行解决
同步共享数据的最简单方法是使用提供同步操作的数据结构。
生产者填装数据->消费者使用数据->消费者告知生产者数据使用完毕->生产者进行选择后续处理(继续装填数据还是停止)
from queue import Queue
queue = Queue()
def synchronized_consume():
while True:
print('got an item:', queue.get())
queue.task_done()#该方法通知Queue已经处理完毕一个项目
def synchronized_produce():
consumer = threading.Thread(target=synchronized_consume, args=())
consumer.daemon = True
consumer.start()
for i in range(10):
queue.put(i)#添加元素
queue.join()#该方法等待知道项目都处理完毕后退出
synchronized_produce()
锁
锁用于解决数据冲突,当一个线程获取资源的锁之后,另一个线程无法再次获取,需等待前一个线程将锁进行释放后才可以获取。
在Python中,threading模块带有Lock功能,并且使用with复合语句自动进行锁的获取和释放。
下列代码中,将名为seen的集合数据资源进行加锁。
seen = set()
seen_lock = threading.Lock()#创建锁
def already_seen(item):
with seen_lock: #自动管理锁:开始使用锁,释放锁
if item not in seen:
seen.add(item)
return False
return True
Barriers屏障
避免共享数据访问冲突的另一种方法是将程序分为多个阶段,确保共享数据在没有其他线程访问它的阶段中发生变化。屏障通过要求所有线程先到达该程序才能继续执行,将程序划分为多个阶段。在屏障之后执行的代码不能与屏障之前执行的代码并发。
在 Python 中, threading
模块以 Barrier
实例的 wait
方法的形式提供屏障:
counters = [0, 0]
barrier = threading.Barrier(2)
def count(thread_num, steps):
for i in range(steps):
other = counters[1 - thread_num]
barrier.wait() # wait for reads to complete
counters[thread_num] = other + 1
barrier.wait() # wait for writes to complete
def threaded_count(steps):
other = threading.Thread(target=count, args=(1, steps))
other.start()
count(0, steps)
print('counters:', counters)
threaded_count(10)
在此示例中,共享数据的读取和写入发生在不同的阶段,并由屏障分隔。写入发生在同一阶段,但它们是不相交的;这种不相交对于避免在同一阶段并发写入相同数据是必要的。由于此代码已正确同步,因此两个计数器最后都将始终为 10
管道
避免共享数据在并发访问时引起的冲突。多个进程所需的任何状态都可以通过管道在进程之间传递消息来进行通信。
在Python中,使用multiprocessing
模块中的 Pipe
类提供进程之间的通信通道。默认情况下,它是双工的,意味着双向通道,但传入参数 False
会导致单向通道。 send
方法通过通道发送对象,而 recv
方法接收对象。后者是阻塞的,这意味着调用 recv
的进程将等待,直到接收到对象。
以下是使用进程和管道的生产者/消费者示例:
def process_consume(in_pipe):
while True:
item = in_pipe.recv()
if item is None:
return
print('got an item:', item)
def process_produce():
pipe = multiprocessing.Pipe(False)
consumer = multiprocessing.Process(target=process_consume, args=(pipe[0],))
consumer.start()
for i in range(10):
pipe[1].send(i)
pipe[1].send(None) # done signal
process_produce()
在此示例中,我们使用 None
消息来表示通信结束。在创建消费者进程时,我们还将管道的一端作为参数传递给目标函数。这是必要的,因为状态必须在进程之间显式共享。
欠同步
并行计算中的一个常见陷阱是忽略正确同步共享访问。在集合示例中,我们需要将成员资格检查和插入同步在一起,以便另一个线程无法在这两个操作之间执行插入。未能将两个操作同步在一起是错误的,即使它们是单独同步的。
过度同步
另一个常见错误是过度同步程序,从而导致非冲突操作无法同时发生。举一个简单的例子,我们可以通过在线程启动时获取主锁并仅在线程完成时释放它来避免对共享数据的所有冲突访问(资源冲突)。这会序列化我们的整个代码,因此没有任何东西可以并行运行。在某些情况下,这甚至可能导致我们的程序无限期挂起。例如,考虑一个消费者/生产者程序,其中消费者获得锁并且永远不会释放它。这会阻止生产者生产任何物品,进而阻止消费者做任何事情,因为它没有什么可消费的
死锁
由于同步机制会导致线程或进程相互等待,因此很容易出现死锁,即两个或多个线程或进程被卡住并等待彼此完成的情况。我们刚刚看到了忽略释放锁会如何导致线程无限期地卡住。但即使线程或进程正确释放锁,程序仍然可能会陷入死锁。
小总结:
1.python中的print为None返回值(所以python不要作为返回函数或表达式);
2.学习了编写高阶函数可以将核心部分和循环部分进行拆分,再利用函数调用进行组合;
3.函数调用过程可视化展示了,python先对全局区数据进行入栈,再后续跟进实际函数调用进行局部数据入栈
4.lambda表达式结构:lambda parmIn:return expr;
5.装饰器@trace,在一个函数头前加上此注解,会在执行该函数前,先执行trace函数。
6.利用标志变量和队列(FIFO特性)解决竞态条件,还有锁用于对共享资源控制权限的分配,阻塞用于不同操作能够按照我们想要的顺序执行,管道用于不同进程间通信和数据传递。