进程和线程
一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程,打开一个记事本就启动了一个记事本进程;在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,把进程内的这些“子任务”称为线程(Thread),比如Word,它可以同时进行打字、拼写检查、打印等事情。线程是最小的执行单元,而进程由至少一个线程组成。
同时执行多个任务:
- 多进程模式:是启动多个进程,每个进程虽然只有一个线程,但多个进程可以一块执行多个任务。
- 多线程模式:是启动一个进程,在一个进程内启动多个线程,这样,多个线程也可以一块执行多个任务。
- 多进程+多线程模式:启动多个进程,每个进程再启动多个线程,这样同时执行的任务就更多了,当然这种模型更复杂,实际很少采用。
计算量大的用多线程,需要读取多的用多进程。
多进程
程序每次执行时,操作系统就会创建一个新的进程来运行程序指令。
在下面的fork()函数运行的时候,fork()函数会复制当前这个进程,然后创建一个子进程。
普通的函数调用就是调用一次,返回一次。但fork()调用一次,返回两次,因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后,分别在父进程和子进程内返回。
子进程永远返回0,而父进程返回子进程的ID。一个父进程可以fork出很多子进程,所以,父进程要记下每个子进程的ID,而子进程只需要调用getppid()就可以拿到父进程的ID。
import os
print('Process (%s) start...' % os.getpid())
pid = os.fork()
if pid == 0:
print('I am child process (%s) and my parent is %s.' % (os.getpid(), os.getppid()))
else:
print('I (%s) just created a child process (%s).' % (os.getpid(), pid))
os.getpid()返回当前进程的ID
os.getppid()返回父进程的ID,有两个p。
这些代码只能在mac和Linux系统上面运行,window运行会报错,因为没有fork()这个方法。
从pid=os.fork()这行里,开始创建了两个进程,就是两个任务,一个是父进程返回的是子进程的ID,而子进程只会返回0,于是两个任务就分别进入if和else语句。但是先打印哪句话(先运行父进程还是子进程,这个是由操作系统决定的)进程调度是操作系统决定的,千万不要在代码里假定哪个先执行。
在windows里,因为没有fork()函数,所以引入multiprocessing模块的Precess类来创建进程对象。
还有一个就是必须得写if name=='main':这个程序入口,不然会陷入无限递归,然后保存。
from multiprocessing import Process
import os
# 子进程要执行的代码(就是任务函数)
def run_proc(name):
print('Run child process %s (%s)...' % (name, os.getpid()))
#这里面没有fork()函数来复制当前进程来创建一个子进程
if __name__=='__main__':#这个__name__='__main__'跟包的引入有关,就记得是程序的入口就行
print('Parent process %s.' % os.getpid())
p = Process(target=run_proc, args=('test',))#所以就自己创建一个子进程,这句话就是自己创建一个子进程的意思
print('Child process will start.')
p.start()#启动自己创建的子进程
p.join()
print('Child process end.')
这里面着重p = Process(target=run_proc, args=('test',))这句话,当创建一个进程,里面需要执行任务,所以target参数就是可以传入需要这个进程执行的代码名,后面的参数args,就是传入的任务函数的参数。创建了进程对象,需要调用start()方法来启动这个进程。
错误抛出:
Python 解释器在 Windows平台 执行创建多进程的程序时,子进程会读取当前 Python 文件,用以创建进程。
在子进程读取当前文件时,读取到创建子进程的代码时又会创建新的子进程,这样程序就陷入递归创建进程的状态。
由于 Python 解释器中对于创建子进程有一个最大数量限制来保护我们的计算机,防止其内存溢出,因此程序会抛出异常。
所以创建进程必须得写if name=='main':这个程序入口里,不然会陷入无限递归,然后保存。
进程池pool
进程池就是可以一下子创建好几个进程在这个池子对象里,然后用这个池子对象执行任务,这个池子对象就会用几个进程来执行任务。
p = Pool(5),就是创建5个进程,但是一般电脑的CPU都是四核的,所以就会等执行完一个进程,再去执行第五个进程。所以进程池创建几个进程就会调用CPU的几个核,要是多于CPU的核,就会等某个进程结束,再执行。
import multiprocessing as mp
import os
def job(x):
return x*x
if __name__ == "__main__":
print("当前进程启动,不是池子里创建的进行,这是个父进程%s"%os.getpid())
p = mp.Pool(5)#这句就是在进程池里创建了5个进程,但是我的电脑CPU里只有四个核
res = p.map(job,range(5))#这个map跟python内置的map用法一样,不同的是自己会返回列表格式
print(type(res))
print(res)
这个就是简单的使用进程池来创建多线程对Job函数使用多个核来执行。
除了pool的map方法,还有一个apply_async(job,(参数,))方法,它同样需要传入任务函数名,必须要在参数后面加逗号,而且必须加括号,说明是个元组(弹幕里说的,还是不能理解),但是不能传入好几个参数,只能传入一个参数,如果要传多个,需要用到列表解析式。
而且要这个apply_async()返回的是一个类对象,必须使用get()才能获取到值。
要apply_async能处理多个参数的()需要使用列表解析式:
由于这个函数只能返回一个类对象,调用get()方法才能获得值,所以用来列表解析式获取的列表里面包含的都是不同的类对象,所以需要再写一个列表解析式迭代这个里面都是类对象的列表,才能获取返回值。
还有一个join()方法,说是阻塞主进程,当调用这个方法的进程结束,主进程才会接着进行,但现在还没用到,先记得有这个方法,遇到再来看。
子进程(subprocess)
subprocess模块可以帮助我们生成一个新的进程,可以让我们在python中调用其他可执行的程序,比如在终端cd切换文件夹啥的。
subprocess提供了一些接口,能帮助我们对这个新的外部进程进行操作,这里学三种主要的。
subprocess.run():
subprocess.Popen():
这个方法就是能够实现和终端交互命令,具体怎么操作,以后再学。
subprocess.call()用的爬取B站视频有这个调用的,具体操作应该也是在shell里面进行命令传输,具体里面的参数,以后会慢慢学到。
进程间的通信
进程之间的通信,python在模块里提供了队列queue和Pipes来实现进程之间的通信。
import multiprocessing as mp
def job(q):
res = 0
for i in range(100):
res = i*100
q.put(res)
if __name__ == "__main__":
q = mp.Queue#创建一个队列对象
p1 = mp.Process(target=job,args=(q,))
p2 = mp.Process(target=job,args=(q,))
p1.start()
p2.start()
p1.join()
p2.join()#join()就记住,就是引用这个方法的进程结束的意思,join里面可以传入参数,就是这个进程运行的时间。
res1 = q.get()
res2 = q.get()
print(res1+res2)
这个代码是在视频里看的比较容易懂,在Windows里不会报错,但是运行就会报错。
文档里面的代码也是比较容易看懂的。
多线程
进程是由若干线程组成的,一个进程至少有一个线程。
当我们启动当前程序,就是启动了一个进程,任何进程都会至少启动一个线程,这个线程叫做主线程,主线程可以启动新的线程,Python的threading模块有个current_thread()函数,这个函数永远返回当前线程的实例对象,所以可以这样threading.current_thread().name来打印线程实例对象的名字,主线程的名字叫做MainThread,我们在创建子线程的时候,除了跟创建进程那样需要传入要执行的任务函数,还多一个给线程命名的参数,比如这样:t = threading.Thread(target=loop, name='LoopThread')#这样就是新创建一个线程,这个线程的名字叫做LoopThread。
import time, threading
# 新线程执行的代码:
def loop():
print('thread %s is running...' % threading.current_thread().name)
n = 0
while n < 5:
n = n + 1
print('thread %s >>> %s' % (threading.current_thread().name, n))
time.sleep(1)
print('thread %s ended.' % threading.current_thread().name)
print('thread %s is running...' % threading.current_thread().name)#这一行是打印当前线程的名字
#这一行是创建了一个子线程,并命名。
t = threading.Thread(target=loop, name='LoopThread')
t.start()
t.join()
print('thread %s ended.' % threading.current_thread().name)
Lock
线程的调度是由操作系统决定的,所以当创建了多个子线程,这些线程交替执行,顺序我们不知道。
跟进程不同的是,在进程中,会对同一个变量进行拷贝,然后放进进程里,不会影响其他进程对这个变量的操作;但是在多线程里,同一个变量会被任何线程给修改。
这是一个多线程的代码,对于这个变量balance,chang_it()函数里只是加一下减一下,最后结果还是0,但是当使用多线程后,多线程的执行是这样的:
对于这个change_it()函数,多个线程交替执行,所以最后结果不一样。所以需要Lock来对这个函数上锁,这样就不会就只会单个线程执行这一段,记得上锁之后必须要释放锁,所以要用try-finally来写
lock.acquire()获取锁;lock.release()释放锁。
GIL锁:Global Interpreter Lock
以后再补充
Threadlocal
一个ThreadLocal变量虽然是全局变量,但每个线程都只能读写自己线程的独立副本,互不干扰。ThreadLocal解决了参数在一个线程中各个函数之间互相传递的问题。
以后再补充。
后面还有需要再补充的,等需要的时候再来补充。
正则表达式
正则表达式是用来匹配字符串的。比如用来判断一串e-mail字符是否规范。用法就是:一、创建一个用来匹配正则表达式,二、去判断输入的是否规范。可以和之前遇到的通配符一起学一下。
\d 匹配数字 、 \w匹配字母或数字 \s 表示空白字符,空格,制表符等 就像这样
00\d 007; 00\w 00A
- .可以匹配任意字符
- *表示任意个字符(包括0个)
- +表示至少一个字符
- ?表示0个或1个字符
- {n}表示n个字符,用{n,m}表示n-m个字符
- ''转义(具体咋用,就是_这样,知道就行。)
进阶
可以用[]表示范围,精确地匹配
- [0-9a-zA-Z_]可以匹配一个数字、字母或者下划线;(前面是数字0到9,所以第一个数字,后面是a到z和A到Z,所以字母,然后加一个转义的下划线_);
- [0-9a-zA-Z_]+可以匹配至少由一个数字、字母或者下划线组成的字符串,比如'a100','0_Z','Py3000'等等;
- [a-zA-Z_][0-9a-zA-Z_]*可以匹配由字母或下划线开头,后接任意个由一个数字、字母或者下划线组成的字符串,也就是Python合法的变量;
- [a-zA-Z_][0-9a-zA-Z_]{0, 19}更精确地限制了变量的长度是1-20个字符(前面1个字符+后面最多19个字符)。
- A|B可以匹配A或B,所以(P|p)ython可以匹配'Python'或者'python'
- 表示行的开头,\d表示必须以数字开头。
- $表示行的结束,\d$表示必须以数字结束。
re模块
由于\本身就有转义的意思,所以为了表示\就用两个\表示,就这样"abc\ABC"来表示"abc\ABC"。但最好还是这样写,在前面加个前缀r,这样r“abc\ABC”。
引入re模块,来进行匹配的判断。
如果匹配成功就会返回match类型的对象,如果匹配失败就会返回None。
切分字符串
正常使用切片的split不能一下子切分多个连续的,所以可以使用正则表达式里面的切片操作。
用法就是引入re模块的split方法,主要,需要以什么分割就写个r"[]"然后再加上要分割的字符串。
分组
m = re.match(r'^(\d{3})-(\d{3,8})$', '010-12345')
m.group(0)
m.group(1)
m.group(2)
当匹配一个对象后,可以使用group()方法来返回子串,group(0)永远返回的是整个字符串。匹配的对象使用groups()对象,就会返回这个字符串。
贪婪匹配
这个就是加了?号的,这个?号就是前面遇到的通配符。
编译
可以使用re.compile(r'^(\d{3})-(\d{3,8})$')来定义一个正则表达式的字符串,这样返回一个Regular Expression对象,下次可以直接用这个对象来匹配字符串。
正则表达式其实还有很多很多东西需要去学习,文档列的就这些,以后用到再去学。
接着后面就是常用的内置模块和第三方模块。
标签:__,进阶,Python,创建,print,线程,进程,name From: https://www.cnblogs.com/huanc/p/17441379.html