进程线程-让你的代码更灵活

学习知识要善于思考,思考,再思考 ---爱因斯坦

进程:车间 线程:worker 互斥锁: 厕所

进程和线程是操作系统所提供的,能让程序再同一时间处理多个任务的方法,让程序能够做到「一心二用」.

关于进程和线程的具体概念,可以参考进程和线程的一个简单解释

进程

当我们运行一个程序时,这个程序的代码会被操作系统加在到内存中,并创建出一个进程来承载和运行它。简单的说,每一个运行中的程序就是一个进程,这个进程被称为主进程。

在主进程中,我们可以创建子进程来协助处理其它事物,这时主进程和子进程是并行运行的。子进程也可以有它的子进程,从而形成以主进程为根的一颗进程树。

我们可以用multiprocessing.Process()方法来创建进程:

import multiprocessing

p = multiprocessing.Process(target=目标函数, args=(目标函数的参数,))

start()方法来启动一个进程

p.start()

来看个例子:

import multiprocessing
import os

def target_func():
    print('子进程运行')
    print('子进程 pid:', os.getpid())
    print('子进程的 ppid:', os.getppid())

p = multiprocessing.Process(target=target_func)
p.start()

print('主进程运行')
print('主进程 pid:', os.getpid())
主进程运行
主进程 pid: 6080
子进程运行
子进程 pid: 6081
子进程的 ppid: 6080
  • 在这个例子中:用 multiprocessing.Process()来创建进程,并为该进程指定要执行的目标函数target_func,进程启动后将执行该函数
  • 使用start()方法来启动一个进程
  • 使用os.getpid()来获取进程的进程ID,它是进程的唯一标识符,用于区分进程
  • 使用os.getppid()获取父进程ID,父进程是创建子进程的进程
  • 主进程pid和子进程的ppid相同,因为主进程是该子进程的父进程

另外可以看到,虽然子进程被创建和启动,但是子进程的print()函数并没有立即执行,反而是主进程的print()函数先执行。这说明进程间的执行顺序是不确定的,并非同步执行.

使用join()方法可以控制子进程的执行顺序。

import multiprocessing
import os

def target_func():
    print('子进程运行')
    print('子进程 pid:', os.getpid())
    print('子进程的 ppid:', os.getppid())

p = multiprocessing.Process(target=target_func)
p.start()
p.join()

print('主进程运行')
print('主进程 pid:', os.getpid())

上述代码新增了p.join(),执行结果如下:

子进程运行
子进程 pid: 6181
子进程的 ppid: 6180
主进程运行
主进程 pid: 6180

线程

每一个进程都默认有一个线程,这个线程被称为主线程。我们可以在主线程中创建其它线程来协助处理任务,这些线程也是并行运行的。

线程是进程的执行单元,CPU调度进程时,实际上是在进程的线程间作切换。另外线程间共享她们所在进程的内存空间(栈除外)

可以使用theading.Thread()方法来创建线程:

import threading

t = threading.Thread(target=目标函数, args=(目标函数的参数,))

start()方法来启动一个线程:

t.start()

来看个例子;

import threading

def target_func(n):
    for i in range(n):
        print(i)

t = threading.Thread(target=target_func, args=(8,))
t.start()

print('主线程结束')

执行:

0
主线程结束
1
2
3
4
5
6
7

上述的子线程和主线程交替执行,可以使用join()让主线程等待子线程执行完成:

import threading

def target_func(n):
    for i in range(n):
        print(i)

t = threading.Thread(target=target_func, args=(8,))
t.start()
t.join()

print('主线程结束')

上述代码新增了t.join(),执行:

0
1
2
3
4
5
6
7
主线程结束

线程锁

多个线程间会共享进程的内存空间,如果多个线程同时修改和访问同一个对象,则可能会出现非预期的错误.

比如下面这个例子中,我们创建了2个进程,这2个进程分别对number变量做一百万次+1操作.

import threading

number = 0

def add():
    for i in range(1000000):
        global number
        number += 1

t_1 = threading.Thread(target=add)
t_2 = threading.Thread(target=add)
t_1.start()
t_2.start()
t_1.join()
t_2.join()

print(number)

number的预期是2000000(两百万)

运行后:

1584652

再运行

1436895

再运行

1584723

可以看到每次运行的结果都不一样,均小于2000000

这是因为,number+1其实是2个操作:

首先获取number,然后对取到的值+1.这2个操作并不是原子的(也就是说,这2个操作并不一定会被CPU连续执行),这行第一个操作时,CPU有可能被中断去执行其它任务,之后又回到这里执行第二个操作)。这个例子中,有一种可能是,执行到某个时刻时,第一个线程获取到number值为100,紧接着第二次线程也获取到number值为100,第一个线程在100的基础上加1,并将101赋值给number,由于两个线程是并发运行的,她们彼此之间并不知情,这样就浪费了一次+1,最终number的结果就变小了。

在这种情况下,要想得到正确的结果,应该对number+=1这个操作加锁。如下:

import threading

number = 0
lock = threading.Lock()

def add():
    for i in range(1000000):
        global number

        lock.acquire()
        number += 1
        lock.release()

t_1 = threading.Thread(target=add)
t_2 = threading.Thread(target=add)
t_1.start()
t_2.start()
t_1.join()
t_2.join()

print(number)

运行

2000000

再运行:

2000000

可以看到这次结果完全正确,但同时我们也能感受到,程序执行的速度变慢了,是的,锁会带来性能上的损耗,这就需要我们能再正确性和性能间做取舍了。

results matching ""

    No results matching ""