进程和线程
多进程
Process
在python中,我们可以通过实例化multiprocessing
模块中的Process
类来创建一个子进程,需要给该该进程传入需要执行的函数和对应的参数。进程创建好后使用方法start()
来启动,在父进程中使用方法jion()
时,父进程会等待子进程执行完后再接着往下执行,同于实现进程的同步操作。来看个栗子就知道怎么操作了:
from multiprocessing import Process
import os
def fun(msg):
print('子进程开始...')
print('我是进程%s, 正在执行任务%s。父进程是%s' % (os.getpid(), msg, os.getppid()))
print('子进程结束...')
if __name__ == '__main__':
print('父进程开始...')
print('父进程号是%s' % os.getpid())
p = Process(target = fun, args = ('A',))
p.start()
p.join() #在此处等待子进程执行完再接着往下执行
print('父进程结束...')
运行结果为:
父进程开始…
父进程号是12096
子进程开始…
我是进程11724, 正在执行任务A。父进程是12096
子进程结束…
父进程结束…
Pool
除了使用Process
可以创建子进程,我们还可以使用Pool
来创建出多个子进程,称为进程池。Pool
中创建并启动进程有两种方法——apply()
和apply_async()
,这两个方法也需传入要执行的函数和对应参数。apply()
方式启动的进程是串行执行的,效率低,而apply_async()
是并发执行的,效率高,因此推荐使用后者!Pool
有个close()
方法,调用了close()
后就不能添加新的进程了,同时也有个join()
方法用于实现进程的同步,这里要注意,调用join()之前必须先调用close(),主进程结束后程序(子进程会强制结束)就会退出。来看个例子:
from multiprocessing import Pool
import os
def fun(msg):
print('task %d: process %s is running and parents process is %s' % (msg, os.getpid(), os.getppid()))
if __name__ == '__main__':
print('process %s starts to run' % os.getpid())
ps = Pool(5)
for i in range(7):
#ps.apply(fun, args = (i,))
ps.apply_async(fun, args = (i,))
ps.close()
ps.join()
print('process %s ends' % os.getpid())
运行结果为:
process 7036 starts to run
task 0: process 8516 is running and parents process is 7036
task 1: process 8516 is running and parents process is 7036
task 2: process 8516 is running and parents process is 7036
task 3: process 12132 is running and parents process is 7036
task 4: process 11680 is running and parents process is 7036
task 5: process 12132 is running and parents process is 7036
task 6: process 8516 is running and parents process is 7036
process 7036 ends
多线程
- 一个进程至少包含一个线程,线程之间是并发执行的,因此执行效率较高。python中使用
threading
模块来操作线程。先创建Thread
实例,然后调用start()
开始执行,threading.current_thread()函数是获得当前线程的实例,主线程实例的名字叫MainThread
,子线程的名字在创建时指定,如果不起名字Python就自动给线程命名为Thread-1,Thread-2……,来看个例子:
import threading, time
#新线程中要执行的函数
def fun():
bgn = time.time() #获得此刻的时间戳
print('线程%s开始运行...' % threading.current_thread().name)
it = (x for x in range(10) if x % 2)
for x in it:
print('线程%s产生数据:%d' % (threading.current_thread().name, x))
time.sleep(1)
end = time.time()
print('线程%s运行结束!运行时间为%fs' % (threading.current_thread().name, end - bgn))
bgn = time.time()
print('主线程开始运行...')
t = threading.Thread(target = fun, name = 'funThread')
t.start()
it = (y for y in range(10) if not y % 2)
for y in it:
print('主线程产生数据: %d' % y)
time.sleep(1)
end = time.time()
print('主线程运行结束!运行时间为%fs' % (end - bgn))
运行结果为:
主线程开始运行…
线程funThread开始运行…
主线程产生数据: 0
线程funThread产生数据:1
线程funThread产生数据:3
主线程产生数据: 2
线程funThread产生数据:5
主线程产生数据: 4
线程funThread产生数据:7
主线程产生数据: 6
主线程产生数据: 8
线程funThread产生数据:9
主线程运行结束!运行时间为5.023202s
线程funThread运行结束!运行时间为5.022726s
- 一个进程的多个线程能够共享变量,因此在操作多线程时需要解决的一个问题是互斥资源的访问,即互斥资源在每次只能由一个线程进行访问,否则就会出现问题,比如看下面的例子:
import threading
money = 0
def chance_money(n):
global money
for i in range(100000000):
money = money + n
money = money - n
t1 = threading.Thread(target = chance_money, args = (5,))
t2 = threading.Thread(target = chance_money, args = (10,))
t1.start()
t2.start()
t1.join()
t2.join()
print('money = %d' % money)
上述代码中,我们每次在加n之后又减n,所以按理说最后得到的money应该是0才对,但最后输出的结果是money = -80。这是因为在给money赋完值后还没来得及进行下一步就被另一个线程中money的赋值给覆盖了。为了解决这个问题,我们就需要给临界区代码上锁,python中使用threanding.lock()
来获得一个锁,然后将临界区代码夹在acquire()
和release()
方法中:
import threading
money = 0
lock = threading.Lock()
def chance_money(n):
global money
lock.acquire() #给临界区上锁
for i in range(100000000):
money = money + n
money = money - n
lock.release() #释放锁
t1 = threading.Thread(target = chance_money, args = (5,))
t2 = threading.Thread(target = chance_money, args = (10,))
t1.start()
t2.start()
t1.join()
t2.join()
print('money = %d' % money)
这样就能保证每次只有获得锁的那个线程才能够修改互斥资源money,现在就不会出现问题了。