Python 进程,线程,协程

本节内容

  1. 进程和线程

  2. python GIL全局解释锁
  3. 线程语法

  4. 进程语法

1.进程与线程

1.1 什么是进程

程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程。程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念。

在多道编程中,我们允许多个程序同时加载到内存中,在操作系统的调度下,可以实现并发地执行。这是这样的设计,大大提高了CPU的利用率。进程的出现让每个用户感觉到自己独享CPU,因此,进程就是为了在CPU上实现多道编程而提出的。

在多道编程中,我们允许多个程序同时加载到内存中,在操作系统的调度下,可以实现并发地执行。正是这样的设计,大大提高了CPU的利用率。进程的出现让每个用户感觉到自己独享CPU,因此,进程就是为了在CPU上实现多道编程而提出的。

有了进程为什么还要线程?

进程有很多优点,它提供了多道编程,让我们感觉我们每个人都拥有自己的CPU和其他资源,可以提高计算机的利用率。很多人就不理解了,既然进程这么优秀,为什么还要线程呢?其实,仔细观察就会发现进程还是有很多缺陷的,主要体现在两点上:

  • 进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。

  • 进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。

直白一点, 一个操作系统就像是一个工厂,工厂里面有很多个生产车间,不同的车间生产不同的产品,每个车间就相当于一个进程,且你的工厂又穷,供电不足,同一时间只能给一个车间供电,为了能让所有车间都能同时生产,你的工厂的电工只能给不同的车间分时供电,但是轮到你的qq车间时,发现只有一个干活的工人,结果生产效率极低,为了解决这个问题,应该怎么办呢?。。。。没错,你肯定想到了,就是多加几个工人,让几个人工人并行工作,这每个工人,就是线程!

1.2 什么是线程

线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务

2.python GIL全局解释锁

Python GIL(Global Interpreter Lock)  

In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)

上面的核心意思就是,无论你启多少个线程,你有多少个cpu, Python在执行的时候会淡定的在同一时刻只允许一个线程运行。

首先需要明确的一点是GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。就好比C++是一套语言(语法)标准,但是可以用不同的编译器来编译成可执行代码。有名的编译器例如GCC,INTEL C++,Visual C++等。Python也一样,同样一段代码可以通过CPython,PyPy,Psyco等不同的Python执行环境来执行。像其中的JPython就没有GIL。然而因为CPython是大部分环境下默认的Python执行环境。所以在很多人的概念里CPython就是Python,也就想当然的把GIL归结为Python语言的缺陷。所以这里要先明确一点:GIL并不是Python的特性,Python完全可以不依赖于GIL

3.线程

线程的用法集合:

  • start  线程准备就绪,等待CPU调度
  • setName  为线程设置名称
  • getName  获取线程名称
  • setDaemon  设置为后台线程或前台线程(默认)
    •   如果是后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,均停止
    •   如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程也执行完成后,程序停止
  • join              逐个执行每个线程,执行完毕后继续往下执行,该方法使得多线程变得无意义
  • run              线程被cpu调度后自动执行线程对象的run方法

3.1 threading模块

线程有2种调用方式,如下

直接调用

import threading
def task(arg):  # 定义每个线程要运行的函数
    print(arg)

for i in range(5):
    t = threading.Thread(target=task,args=(i,))  # 生成一个线程实例
    t.start()  # 启动线程
    print(t.getName())  # 获取线程名
print('end')

继承调用

import threading

class Mythread(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.num = enumerate
    def run(self):# 定义每个线程要运行的函数
        print('running on number:%s'%self.name)
        
if __name__ == '__main__':
    for i in range(10):
        t = Mythread(i)
        t.start()

3.2 线程锁(Lock,RLock)

由于线程之间是进行随机调度,并且每个线程可能只执行n条执行之后,当多个线程同时修改同一条数据时可能会出现脏数据,所以,出现了线程锁

- 同一时刻允许一个线程执行操作。

import threading
import time

def sayhi(num):
    lock.acquire()
    time.sleep(1)
    print('running %s'%num)
    lock.release()

if __name__ == '__main__':
    lock = threading.Lock() # 普通的锁
    for i in range(10):
        t = threading.Thread(target=sayhi,args=(i,)) # 创建进程
        print(t.getName())
        t.start()
    print('主程序'.center(20,'-'))
import threading
import time

def sayhi(num):
    lock.acquire()
    lock.acquire()
    time.sleep(1)
    print('running %s'%num)
    lock.release()
    lock.release()

if __name__ == '__main__':
    lock = threading.RLock()  # 递归锁
    for i in range(10):
        t = threading.Thread(target=sayhi,args=(i,)) # 创建进程
        print(t.getName())
        t.start()
    print('主程序'.center(20,'-'))

3.2 信号量(Semaphore)

互斥锁同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据 ,比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去。

import threading
import time

def sayhi(num):
    lock.acquire()
    # lock.acquire()
    time.sleep(1)
    print('running %s'%num)
    lock.release()
    # lock.release()

if __name__ == '__main__':
    lock = threading.BoundedSemaphore(2)  # 同时允许两个人操作数据
    for i in range(10):
        t = threading.Thread(target=sayhi,args=(i,)) # 创建进程
        print(t.getName())
        t.start()
    print('主程序'.center(20,'-'))

3.3 事件(event)

python线程的事件用于主线程控制其他线程的执行,事件主要提供了三个方法 set、wait、clear。

事件处理的机制:全局定义了一个“Flag”,如果“Flag”值为 False,那么当程序执行 event.wait 方法时就会阻塞,如果“Flag”值为True,那么event.wait 方法时便不再阻塞。

  • clear:将“Flag”设置为False
  • set:将“Flag”设置为True
import threading

def sayhi(num):
    event.wait()  # 锁住所有线程
    print('running :%s'%num)

if __name__ == '__main__':
    event = threading.Event()
    for i in range(10):
        t = threading.Thread(target=sayhi,args=(i,))
        t.start()
    event.clear()
    string = input(':>>')
    if string == 'true':
        event.set()  # 打开锁,允许所有线程通过

3.4 条件(Condition)

使得线程等待,只有满足某条件时,才释放n个线程

import threading
import time
lock = threading.Condition()  # 指定解锁的进程个数
def take(arg):
    time.sleep(1)
    lock.acquire()
    # 锁住所有的线程
    lock.wait()
    print(arg)
    lock.release()
for i in range(10):
    t = threading.Thread(target=take,args=(i,))
    t.start()

while True:
    value = input('>>')
    lock.acquire()
    lock.notify(int(value))
    lock.release()

3.5 Timer

定时器,指定n秒后执行某操作

import threading
import time
def sayhi(num):
    time.sleep(5)
    print('running :%s'%num)

if __name__ == '__main__':
    for i in range(10):
        t = threading.Timer(1,sayhi,args=(i,))
        t.start()

4 进程

multiprocessing是python的多进程管理包,和threading.Thread类似。

4.1 定义进程

import multiprocessing  # 导入函数
import time
def foo(i): # 定义进程执行的方法
time.sleep(2)
print('say hi',i)

if __name__ == '__main__':
for i in range(10):
p = multiprocessing.Process(target=foo, args=(i,)) # 创建进程
p.start() # 执行进程
# p.join() # 等待进程执行完毕

进程的使用方法和线程的基本一致,可以参考上面说的,现在主要说说进程间通信。

4.2 进程间通信

在使用并发设计的时候最好尽可能的避免共享数据,尤其是在使用多进程的时候。 如果你真有需要 要共享数据, multiprocessing提供了两种方式。

不同进程间内存是不共享的,要想实现两个进程间的数据交换,可以用以下方法:

# 该例可以看出进程间的是不能进行数据交换的
import multiprocessing
import time
li = []

def sayhi(num):
li.append(num)
print('hi',num)

if __name__ == '__main__':
for i in range(3):
p = multiprocessing.Process(target=sayhi,args=(i,))
p.start()
p.join()
time.sleep(1)
print(li)
# 输出 # hi 0 # hi 1 # hi 2 # []

第一种: 

(1)multiprocessing,Array,Value  

Value的使用参考http://www.cnblogs.com/aylin/p/5601969.html

数据可以用Value或Array存储在一个共享内存地图里,如下:

# 方法一,Array
from multiprocessing import Process, Array
# Array调用c语言底层,需要制定类型,列表元素必须统一。
temp = Array('i', [11, 22, 33, 44]) 

def Foo(i):
    temp[i] = 100 + i
    for item in temp:
        print(i, '----->', item)

if __name__ == '__main__':
    for i in range(2):
        p = Process(target=Foo, args=(i,))
        p.start()

第二种:

(2)multiprocessing,Manager

由Manager()返回的manager提供list, dict, Namespace, Lock, RLock, Semaphore, BoundedSemaphore, Condition, Event, Barrier, Queue, Value and Array类型的支持

from multiprocessing import Process,Manager

def task(num,li):  # 把列表传给函数
    li.append(num)
    print(li)
if __name__ == '__main__':
    v = Manager().list()  # 通过Manager生成列表
    # v = Manager().dict()
    for i in range(10):
        p = Process(target=task,args=(i,v,))
        p.start()
        p.join()  # 串行执行

线程池,进程池,还有协程下节讲。

最新内容可以看我的blog: 40kuai
原文地址:https://www.cnblogs.com/40kuai/p/6612651.html