107、进程与线程

进程、线程、与协成

1.1 进程介绍

程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程

程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念。

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

进程的缺点:

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

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

1.2 线程介绍

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

1.3 线程和进程的区别

  • 进程的内存独立,线程共享同一进程的内存
  • 进程是资源的集合,线程是执行单位
  • 进程之间不能之间互相访问,线程可以相互通信
  • 创建新进程非常消耗资源,线程非常轻量,只保存线程需要运行时的必要数据,如上下文
  • 同一进程里的线程可以相互控制,父线程可以控制子线程
  • io 密集型应用使用多线程,
  • 计算密集型应用使用多进程

1.4 GIL 锁

python 中某一进程中的多个线程只能被一个 CPU 调用,因为在线程中存在 GIL(全局解释器锁)。而C语言一个进程的多线程可以分别被不同的 CPU 调用

2、threading模块

2.1 线程的继承

import threading
import time

class MyThread(threading.Thread):
    def __init__(self,num):
        threading.Thread.__init__(self)
        self.num = num
    def run(self):#定义每个线程要运行的函数
        print("running on number:%s" %self.num)
        time.sleep(3)

if __name__ == '__main__':
    t1 = MyThread(1)
    t2 = MyThread(2)
    t1.start()
    t2.start()

2.2 创建多线程与join

import threading,time

def hellow(msg):
    print('this is hellow %s' %msg)
    time.sleep(1)

if __name__ == '__main__':
    thread_list = []

    for i in range(10):
        t1 = threading.Thread(target=hellow,args=(i,))#生成一个线程
        t1.start()
        thread_list.append(t1)
        print(t1.getName())#获取线程名
    for r in thread_list:
        r.join()#等待该线程执行完毕再往下执行,join(2)就是等待的最长时间
    print('-------主线程-------')

2.3 守护线程Daemon

import threading,time
'''守护线程会在主线程执行完毕时立刻断开'''
def sayhi(msg):

    time.sleep(0.001)
    print('this is msg',msg)

if __name__ == '__main__':
    for i in range(10):
        t1 = threading.Thread(target=sayhi,args=(i,))
        t1.setDaemon(True)#将 t1 设为主线程的守护线程
        t1.start()
    print('------主线程------')

每次输出的结果会有不同,有的线程没执行完就跟着主线程down了

2.4 小结

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

2.5 线程锁

单线程锁 Lock、RLock
import threading,time

num = 10
#lock = threading.Lock()#创建线程锁,一次只能锁一个,只能一次申请锁
lock = threading.RLock()#创建递归锁,一次只能锁一个,多次次申请锁
#lock = threading.BoundedSemaphore(3)#创建信号量锁,一次锁定3人
def task(arg):
    lock.acquire()#申请使用锁
    lock.acquire()#RLock多次申请使用锁,递归锁
    time.sleep(1)
    global num
    num -= 1
    print(num)
    lock.release()#释放锁
    lock.release()#RLock多次释放锁

for i in range(10):
    t = threading.Thread(target=task,args=[i,])
    t.start()

print('-----主线程-----',num)
多线程 Semaphore
lock = threading.BoundedSemaphore(3)#三线程锁

def task(arg):
    # 申请使用锁,其他人等
    lock.acquire()
    time.sleep(1)
    global v
    v -= 1
    print(v)
    # 释放
    lock.release()
    
for i in range(10):
    t = threading.Thread(target=task,args=(i,))
    t.start()
事件 event

python线程的事件用于主线程控制其他线程的执行,事件主要提供了三个方法 set、wait、clear。
事件处理的机制:全局定义了一个“Flag”,如果“Flag”值为 False,那么当程序执行 event.wait 方法时就会阻塞,如果“Flag”值为True,那么event.wait 方法时便不再阻塞

  • clear:将“Flag”设置为False
  • set:将“Flag”设置为True
lock = threading.Event()

def task(arg):
    time.sleep(1)
    # 锁住所有的线程
    lock.wait()
    # 申请使用锁,其他人等
    print(arg)

for i in range(10):
    t = threading.Thread(target=task,args=(i,))
    t.start()
while True:
    value = input('>>>>')
    if value == '1':
        lock.set()
        lock.clear()
条件 Condition

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

lock = threading.Condition()

def task(arg):
    time.sleep(1)
    # 锁住所有的线程
    lock.acquire()
    lock.wait()
    # 申请使用锁,其他人等
    print('线程',arg)
    lock.release()
    
for i in range(10):
    t = threading.Thread(target=task,args=(i,))
    t.start()
while True:
    value = input('>>>>')
    lock.acquire()
    lock.notify(int(value))
    lock.release()
定时器 Timer

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

from threading import Timer

def hello():
    print("hello, world")

t = Timer(1, hello)
t.start()  # after 1 seconds, "hello, world" will be printed

2.5 线程池

from concurrent.futures import ThreadPoolExecutor
import time,requests

pool = ThreadPoolExecutor(2)#定义线程池每次最多运行两个线程
url_list = [
    'http://www.baidu.com',
    'http://www.oldboyedu.com',
    'http://www.qq.com',
 ]

def download(url):
    response = requests.get(url)
    #print(response)
    return response

def option(future):
    download_response = future.result()
    print('option',download_response.url,download_response.status_code,download_response.text)

for url in url_list:
    print('开始请求',url)
    future = pool.submit(download,url)#去线程池获取连接
    future.add_done_callback(option)#调用option函数,并将download的结果传参

3、multiprocessing 模块

进程各自持有一份数据,默认无法共享数据

from multiprocessing import Process
import time

def option(num,li):
    li.append = num
    print(li)

if __name__ == '__main__':
    v = []
    # for i in range(5):
    #     p = threading.Thread(target=option,args=(i,v,))
    #     p.start()
    for i in range(5):
        p1 = Process(target=option,args=(i,v,))
        p1.start()

3.1 进程间数据共享

方法一:Array

from multiprocessing import Process,Array,Manager
import threading

def option(num,li):
    li[num] = 1
    print(list(li))

if __name__ == '__main__':
    v = Array('i',5)#('数据类型',长度)
    for i in range(5):
        p = Process(target=option,args=(i,v,))
        p.start()

方法二:manage.dict()共享数据

from multiprocessing import Process,Array,Manager
import threading

def option(num,li):
    li.append(num)
    print('打印信息',li)

if __name__ == '__main__':
    v = Manager().list()
    for i in range(10):
        p = Process(target=option,args=(i,v,))
        p.start()
        p.join()

3.2 进程池

进程锁与线程锁一样
进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进进程,那么程序就会等待,直到进程池中有可用进程为止。

from concurrent.futures import ProcessPoolExecutor
from multiprocessing import Process,Manager

def call(arg):
    data = arg.result()
    print(data)

def option(arg):
    print('打印信息',arg)
    return arg + 100
if __name__ == '__main__':
    pool = ProcessPoolExecutor(5)
    for i in range(10):
        obj = pool.submit(option,i)
        obj.add_done_callback(call)

4、协成

协程存在的意义:对于多线程应用,CPU通过切片的方式来切换线程间的执行,线程切换时需要耗时(保存状态,下次继续)。协程,则只使用一个线程,在一个线程中规定某个代码块执行顺序。

协程的适用场景:当程序中存在大量不需要CPU的操作时(IO),适用于协程;

from greenlet import greenlet

def test1():
    print(12)
    gr2.switch()
    print(34)
    gr2.switch()

def test2():
    print(56)
    gr1.switch()
    print(78)

gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()

根据协程二次开发:协程+IO

from gevent import monkey; monkey.patch_all()
import gevent
import requests

def f(url):
    response = requests.get(url)
    print(response.url,response.status_code)

gevent.joinall([
        gevent.spawn(f, 'http://www.oldboyedu.com/'),
        gevent.spawn(f, 'http://www.baidu.com/'),
        gevent.spawn(f, 'http://github.com/'),
])
原文地址:https://www.cnblogs.com/workhorse/p/6565296.html