进程与线程

908 阅读8分钟

题目来源GitHub

谈谈你对多进程,多线程,以及协程的理解,项目是否用?

进程:一个运行的程序(代码)就是一个进程,没有运行的代码叫程序,进程是系统资源分配的最小单位,进程拥有自己独立的内存空间,所有进程间数据不共享,开销大。

线程: cpu调度执行的最小单位,也叫执行路径,不能独立存在,依赖进程存在,一个进程至少有一个线程,叫主线程,而多个线程共享内存(数据共享,共享全局变量),从而极大地提高了程序的运行效率。

协程: 是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操中栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。

什么是多线程竞争?

线程是非独立的,同一个进程里线程是数据共享的,当各个线程访问数据资源时会出现竞争状态即:数据几乎同步会被多个线程占用,造成数据混乱,即所谓的线程不安全。

那么怎么解决多线程竞争问题?---锁

锁的好处: 确保了某段关键代码(共享数据资源)只能由一个线程从头到尾完整地执行能解决多线程资源竞争下的原子操作问题。

锁的坏处: 阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了

锁的致命问题: 死锁

什么是锁,有哪几种锁?

锁(Lock)是python提供的对线程控制的对象。有互斥锁,可重入锁,死锁。

什么是死锁?

若干子线程在系统资源竞争时,都在等待对方对某部分资源解除占用状态,结果是谁也不愿先解锁,互相干等着,程序无法执行下去,这就是死锁。

GIL锁 全局解释器锁

作用: 限制多线程同时执行,保证同一时间只有一个线程执行,所以cython里的多线程其实是伪多线程!

所以python里常常使用协程技术来代替多线程,协程是一种更轻量级的线程。

进程和线程的切换时由系统决定,而协程由我们程序员自己决定,而模块gevent下切换是遇到了耗时操作时才会切换

三者的关系:进程里有线程,线程里有协程。

多线程交互访问数据,如果访问到了就不访问了?

怎么避免重读? 创建一个已访问数据列表,用于存储已经访问过的数据,并加上互斥锁,在多线程访问数据的时候先查看数据是否在已访问的列表中,若已存在就直接跳过。

什么是线程安全,什么是互斥锁?

每个对象都对应于一个可称为’互斥锁‘的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。

同一进程中的多线程之间是共享系统资源的,多个线程同时对一个对象进行操作,一个线程操作尚未结束,另一线程已经对其进行操作,导致最终结果出现错误,此时需要对被操作对象添加互斥锁,保证每个线程对该对象的操作都得到正确的结果。

下面几个概念:同步,异步,阻塞,非阻塞?

同步: 多个任务之间有先后顺序执行,一个执行完下个才能执行。

异步: 多个任务之间没有先后顺序,可以同时执行,有时候一个任务可能要在必要的时候获取另一个同时执行的任务的结果,这个就叫回调!

阻塞: 如果卡住了调用者,调用者不能继续往下执行,就是说调用者阻塞了。

非阻塞: 如果不会卡住,可以继续执行,就是说非阻塞的。

同步异步相对于多任务而言,阻塞非阻塞相对于代码执行而言。

什么是僵尸进程和孤儿进程?怎么避免僵尸进程?

孤儿进程: 父进程退出,子进程还在运行的这些子进程都是孤儿进程,孤儿进程将被init 进程(进程号为1)所收养,并由init 进程对他们完成状态收集工作。

僵尸进程: 进程使用fork 创建子进程,如果子进程退出,而父进程并没有调用wait 获waitpid 获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中的这些进程是僵尸进程。

避免僵尸进程的方法:

1.fork 两次用孙子进程去完成子进程的任务

2.用wait()函数使父进程阻塞

3.使用信号量,在signal handler 中调用waitpid,这样父进程不用阻塞

python中进程与线程的使用场景?

多进程适合在CPU密集操作(cpu操作指令比较多,如位多的的浮点运算)。

多线程适合在IO密性型操作(读写数据操作比多的的,比如爬虫)

线程是并发还是并行,进程是并发还是并行?

线程是并发,进程是并行;

进程之间互相独立,是系统分配资源的最小单位,同一个进程中的所有线程共享资源。

并行(parallel)和并发(concurrency)?

并行: 同一时刻多个任务同时在运行

不会在同一时刻同时运行,存在交替执行的情况。

实现并行的库有: multiprocessing

实现并发的库有: threading

程序需要执行较多的读写、请求和回复任务的需要大量的IO操作,IO密集型操作使用并发更好。

CPU运算量大的程序,使用并行会更好

IO密集型和CPU密集型区别?

IO密集型: 系统运行,大部分的状况是CPU在等 I/O(硬盘/内存)的读/写

CPU密集型: 大部分时间用来做计算,逻辑判断等CPU动作的程序称之CPU密集型。

python asyncio的原理?

asyncio这个库就是使用python的yield这个可以打断保存当前函数的上下文的机制, 封装好了selector 摆脱掉了复杂的回调关系

进程

from multiprocessing import Process
# 创建进程
p = Process(target=run1, args=("handsome",18))
# 启动进程
p.start()
# 父进程的结束不能影响子进程,所以等待子进程结束父进程在结束
p.join()

进程池

from multiprocessing import Pool
#进程池
p = Pool()
for i in range(10):
    p.apply_async(run,args=(i,))
p.close()   
p.join()

线程

import threading
t = threading.Thread(target=run)
t.start()

t.join()

解决线程间数据混乱问题

方法一 加线程锁

import threading
lock = threading.Lock()
with lock:
    #数据处理代码

方法二 创建全局ThreadLocal对象

from threading import local
localvar = threading.local()
#特点用来保存一个全局变量,但是这个全局变量只有在当前线程才能访问
#作用就是为每个线程开辟一个独立的空间进行数据存储。

信号量控制线程数量

import threading, time
sem = threading.Semaphore(2)   # 初始化信号量,数量为2
def run():
    with sem:
        for i in range(10):
            print("线程%s打印%d"%(threading.current_thread().name, i))
            time.sleep(1)
if __name__ == "__main__":
    for i in range(5):
        threading.Thread(target=run).start()
        
#获得信号量,信号量减一
#sem.acquire()
#释放信号量,信号量加一
#sem.release()

凑一定数量的线程一起执行

import threading, time
#设置了一个线程数量障碍,当等待的线程到达了这个数量就会唤醒所有的等待线程。
bar = threading.Barrier(3)
def run():
    print("--%s--start"%threading.current_thread().name)
    bar.wait()
    print("--%s---end" % threading.current_thread().name)
    
for i in range(5):
    t = threading.Thread(target=run)
    time.sleep(1)
    t.start()

定时线程

import threading, time
def run():
    print("sunck is a good man")

if __name__ == "__main__":
    #5s后执行该线程
    t = threading.Timer(5,run)
    t.start()
    t.join()
    print("结束")

线程调度

有一类线程需要满足条件之后才能够继续执行, Python提供了threading.Condition 对象用于条件变量线程的支持,

wait([timeout]):线程挂起,直到收到一个notify通知或者超时(可选的,浮点数,单位是秒s)才会被唤醒继续运行。wait()必须在已获得Lock前提下才能调用,否则会触发RuntimeError。调用wait()会释放Lock,直至该线程被Notify()、NotifyAll()或者超时线程又重新获得Lock.

notify(n=1):通知其他线程,那些挂起的线程接到这个通知之后会开始运行, 默认是通知一个正等待该condition的线程,最多则唤醒n个等待的线程。notify()必须在已获得Lock前提下才能调用,否则会触发RuntimeError。notify()不会主动释放Lock。

notifyAll(): 如果wait状态线程比较多,notifyAll的作用就是通知所有线程(这个一般用得少)

lock_con=threading.Condition([Lock/Rlock]): 锁是可选选项, 默认创建一个RLock(),一般都用默认。

import threading, time

cond = threading.Condition()

def run1():
    with cond:
        for i in range(0, 10, 2):
            print("--%s--%d"%(threading.current_thread().name,i))
            time.sleep(1)
            cond.notify()
            cond.wait()

def run2():
    with cond:
        for i in range(1, 10, 2):
            print("--%s--%d"%(threading.current_thread().name,i))
            time.sleep(1)
            cond.notify()
            cond.wait()

if __name__ == "__main__":
    t1 = threading.Thread(target=run1)
    t2 = threading.Thread(target=run2)
    t1.start()
    t2.start()
    t1.join()
    t2.join()

asyncio 异步IO

实例

import asyncio

async def say_hello():  #把一个generator(生成器)标记为coroutine类型,就是所谓的协程
    print('Hello,asyncio !!!')
    # 异步调用asyncio.sleep(1):
    r = await asyncio.sleep(1)
    print('Hello,again')

# 获取EventLoop
loop = asyncio.get_event_loop();
#将generator(协程)丢进EventLoop中执行
loop.run_until_complete(say_hello())
loop.close()