首页 > 系统相关 >如何快速理解多线程与多进程及相关知识点的运用?

如何快速理解多线程与多进程及相关知识点的运用?

时间:2023-02-27 21:11:47浏览次数:55  
标签:__ 知识点 self print 线程 进程 多线程 def

一、多线程与多线程的基本原理

  在一台计算机中打开浏览器浏览网页时,可以发现打开多个网页,可以将其粗略理解为多线程。同时,在计算机浏览网页的同时打开QQ、微信等其他软件,这就是多进程的体现。

(1)多线程

1、多线程的含义

  说起多线程就不得提起线程,而提起线程就不得提起多进程。还是浏览器的例子,一个浏览器可以看作一个独立运行到的程序单位,为什么有网页可以听歌而有的网页可以放视频,原理就是放歌的网页是一个线程,而放视频的网页也是一个线程,这就是线程的体现,一个浏览器可以同时运行行听歌和放视频的网页,这就是多线程的体现。而进程也就是线程的集合,即进程是由一个或多个线程构成的。线程是操作系统进行运算调度的最小单元。而进程是操作系统中的资源单位。

2、并发和并行

  • 并发

  并发是指多个线程对应的多条指令被快速轮换地执行。如一个处理器,在执行线程A地指令一段时间,再执行线程B的指令一段时间,然后再切回线程A执行一段时间。处理器连续不断的在多个线程之间切换和执行,每个线程的执行都会占用这个处理器的一个时间片段,因此同一时刻其实只有一个线程被执行。这就是并发。

  • 并行

  并行是指同一时刻有多条指令在多个处理器上同时执行,这意味着并行必须依赖多个处理器,不论是从宏观还是微观上看,多个线程都是在同一时刻一起执行的。这就是并行。

并行的特点

  并行只能存在于多处理器系统中,因此如果计算机处理器只有一个核,就不可能实现并行。而并行在单处理器和多处理器系统中都可以存在,因为仅靠一个核,就可以实现并发。

3、多线程使用场景

  在一个程序的进程中,一些操作是比较耗时或者等待的,如等待数据库的查询结果返回、爬虫中请求网页数据时等待服务器的响应。在等待期间处理器很明显可以做其他事情,或者执行其他线程,从而提高整体的执行效率。

  • IO密集型任务

  网络爬虫就是一个典型的例子,在向网页发送请求之后,必须等待服务器返回响应,这种任务就属于IO密集型任务。若启用多线程,那么处理器就可以在等待网页响应的同时去执行其他线程,从而提高爬虫效率。

  • 计算机密集型任务

  计算机密集型任务也称CPU密集型任务,是指任务的运行一直需要处理器的参与大量计算工作。此时若开启多线程,处理器从计算机密集型任务切换到另一个密集型任务,处理器是不会停下来的,并且需要处理的任务计算总量是不变的,处理器始终忙于计算,这样并不会节省整体时间,反而在切换线程的过程中耗费更多时间,使得整体效率变低。

  综上,在多线程的使用场景中适用于IO密集型任务,使用多线程可以大大提高程序整体效率。

(2)多进程

  以上关于多进程的介绍较为清晰,在下面将不会太多进行介绍多进程的基本原理。

1、多进程的含义

  多进程是指具有一定独立功能的程序在某个数据集合上的一次运行活动,是系统进行资源分配和调度的一个独立单位。

2、python中多线程和多进程

  python中的多进程比多线程更有优势。

原因

  • GIL(Global InterpreterLock)全局解释器锁。其设计之处是出于对数据安全的考虑,但在python中GIL的限制导致不论是在单核还是多核条件下,同一时刻这使得Python多线程无法发挥多核并行的优势。
  • Python中多线程的执行三步骤:
  1. 获取GIL。
  2. 执行对应线程的代码。
  3. 释放GIL。

  如上所示,每个线程所想执行,必须先拿到GIL,若拿不到,则不允许运行。这样会导致即使在多核条件下,一个python进程中的多个线程在同一时刻也只能执行一个。

  但对于多进程而言,就比较有优势,因为每个进程都有一个自己的GIL,所以在多核处理器下,多进程的运行是不会受GIL影响的。这样多进程的多核优势就可以发挥出来。

注意点

1、进程是系统进行资源分配和调度的独立单位,所以各进程之间的数据是无法共享的如多个进程无法共享一个全局变量,进程之间的数据共享需要由单独的机制来实现。

2、对于爬虫这种IO密集型任务来说,多线程和多进程产生的影响差别并不大,但对于计算密集型任务来说,由于GIL的存在,Python多线程的整体运行效率在多核情况下可能反而比单核更低,而python的多进程相比多线程,运行效率在多核情况下比单核会有成倍提升。

二、多线程与多进程的运用

(1)多线程的相关知识运用

  Python 中使用线程有两种方式:函数或者用类来包装线程对象。

1、函数式:创建多线程。

import threading
import time

toping=True
def main_threading():
    while toping:
        print("线程开始....")
        time.sleep(2)


def control_threding():
    global toping
    while toping:
        command=str(input("线程是否停止:"))
        if command =="exit":
            toping = False
            print("线程已停止,程序结束!")

worked_thread= threading.Thread(target=main_threading)
worked_thread.start()

control_treading=threading.Thread(target=control_threding)
control_treading.start()

  效果如下:

2、线程类(lock锁机制)

   使用 Thread 对象的 Lock 和 Rlock 可以实现简单的线程同步,这两个对象都有 acquire 方法和 release 方法,对于那些需要每次只允许一个线程操作的数据,可以将其操作放到 acquire 和 release 方法之间。可以有效防止资源竞争的情况。

  锁定前:

import threading

num = 100

def func1():
    for i in range(200):
        global num
        if num > 0:
            print("func1-正在输出{}".format(num))
        num -= 1

def func2():
    for i in range(200):
        global num
        if num > 0:
            print("func2-正在输出{}".format(num))
        num -= 1

def func3():
    for i in range(200):
        global num
        if num > 0:
            print("func3-正在输出{}".format(num))
        num -= 1

def start():
    t1 = threading.Thread(target=func1)
    t1.start()
    t2 = threading.Thread(target=func2)
    t2.start()
    t3 = threading.Thread(target=func2)
    t3.start()

if __name__ == '__main__':
    start()

  若不上锁则会出现资源竞争,func1在输出后还没来得及换行就被func2同时输出数据的情况,并且出现两个线程同时修改一个全局变量的现象,如图所示,函数func1和func2同时修改了全局变量为88。

  上锁之后:

import threading

num = 100
lock = threading.Lock()
def func1():
    for i in range(200):
        global num
        lock.acquire()
        if num > 0:
            print("func1-正在输出{}".format(num))
        num -=1
        lock.release()
def func2():
    for i in range(200):
        global num
        lock.acquire()
        if num > 0:
            print("func2-正在输出{}".format(num))
        num -=1
        lock.release()
def func3():
    for i in range(200):
        global num
        lock.acquire()
        if num > 0:
            print("func3-正在输出{}".format(num))
        num -=1
        lock.release()
if __name__ == '__main__':

    t1 = threading.Thread(target=func1)
    t1.start()
    t2 = threading.Thread(target=func2)
    t2.start()
    t3 = threading.Thread(target=func3)
    t3.start()

  从下图可以看到func1在锁定修改后,是不会出现资源竞争,在func1修改期间func2并未修改且同排输出数据,而是在func1释放数据后,func2进行锁定修改。

3、join()阻塞

  • join()可以使得其方法下的代码只有等待前面的函数方法或代码执行完成后才会执行join()后面的函数或代码。
# join()方法测试
# join方法:将join()以上的方法执行完毕后进行,才会执行join()方法后的代码,测试如下。
import threading
import time

# 共享变量
value=[]

# 线程体函数
def thread_body():
    print("t1子线程开始...")
    for i in range(5):
        print('t1子线程执行...')
        value.append(i)
        time.sleep(2)
    print("t1子线程结束")
def thread_body2():
    print("t2子线程开始...")
    for i in range(2):
        print('t2子线程执行...')
        value.append(i)
        time.sleep(2)
    print("t2子线程结束")
# 主线程
print("主线程开始执行...")
# 创建线程对象1
t1=threading.Thread(target=thread_body)
t1.start()
t1.join()

print('value={0}'.format(value))
print('主线程继续执行!')
t2=threading.Thread(target=thread_body2)
t2.start()
t2.join()
print('value={0}'.format(value))
print("全部线程结束")

  效果如下图所示,t1子线程执行结束后,才会执行t2子线程,而t2子线程执行结束后,才会执行主线程,从而完成所有线程的执行。

4、Queue线程优先级队列

  Python 的 Queue 模块中提供了同步的、线程安全的队列类,包括 FIFO(先入先出) 队列 Queue,LIFO(后入先出)队列 LifoQueue,和优先级队列 PriorityQueue。这些队列都实现了锁原语,能够在多线程中直接使用。可以使用队列来实现线程间的同步。

  Queue 模块中的常用方法:

  • Queue.qsize () 返回队列的大小
  • Queue.empty () 如果队列为空,返回 True, 反之 False
  • Queue.full () 如果队列满了,返回 True, 反之 False
  • Queue.full 与 maxsize 大小对应
  • Queue.get ([block [, timeout]]) 获取队列,timeout 等待时间
  • Queue.get_nowait () 相当 Queue.get (False)
  • Queue.put (item) 写入队列,timeout 等待时间
  • Queue.put_nowait (item) 相当 Queue.put (item, False)
  • Queue.task_done () 在完成一项工作之后,Queue.task_done () 函数向任务已经完成的队列发送一个信号
  • Queue.join () 实际上意味着等到队列为空,再执行别的操作

一个简单的队列代码案例:

  • 队列中关于put和get方法的使用
import multiprocessing
import time

def down_load(queue):

    list1 =[1,2,3,4,5,6,7,8,9,0]
    for li in list1:
        queue.put(li)
    print("数据添加完成!")

def parse_data(queue):
    list_data = []
    while True:
        data = queue.get()
        list_data.append(data)
        # time.sleep(0.01)
        if queue.empty():
            break
    print(list_data)

def main():
    queue = multiprocessing.Queue()
    q1 = multiprocessing.Process(target=down_load,args=(queue,))
    q2 = multiprocessing.Process(target=parse_data,args=(queue,))
    q1.start()
    q2.start()

if __name__ == '__main__':
    main()

  效果如下:

  知识拓展:

  • 关于锁机制、队列的使用和join()阻塞方法的使用案例
# -*- coding: UTF-8 -*-

import Queue
import threading
import time

exitFlag = 0

class myThread (threading.Thread):
    def __init__(self, threadID, name, q):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.q = q
    def run(self):
        print "Starting " + self.name
        process_data(self.name, self.q)
        print "Exiting " + self.name

def process_data(threadName, q):
    while not exitFlag:
        queueLock.acquire()
        if not workQueue.empty():
            data = q.get()
            queueLock.release()
            print "%s processing %s" % (threadName, data)
        else:
            queueLock.release()
        time.sleep(1)

threadList = ["Thread-1", "Thread-2", "Thread-3"]
nameList = ["One", "Two", "Three", "Four", "Five"]
queueLock = threading.Lock()
workQueue = Queue.Queue(10)
threads = []
threadID = 1

# 创建新线程
for tName in threadList:
    thread = myThread(threadID, tName, workQueue)
    thread.start()
    threads.append(thread)
    threadID += 1

# 填充队列
queueLock.acquire()
for word in nameList:
    workQueue.put(word)
queueLock.release()

# 等待队列清空
while not workQueue.empty():
    pass

# 通知线程是时候退出
exitFlag = 1

# 等待所有线程完成
for t in threads:
    t.join()
print "Exiting Main Thread"

  同时执行三个线程,而三个线程的执行完毕后,则关闭所有子线程退出主线程,效果如下:

(2)多进程的相关知识运用

  多进程的主要模块为multiprocessing,在这里主要讲的有自定义继承Process类、daemon属性、join()方法、Lock()锁机制、Queue队列、Pool进程池(阻塞与非阻塞、进程结果再处理、map函数多网页抓取数据)等方法。

1、一个简单的多进程示例

  • 其中target 传入函数名,args 是函数的参数,是元组的形式,如果只有一个参数,那就是长度为 1 的元组。
import multiprocessing
def process(num):
    print('process:',num)

if __name__ == '__main__':
    for i in range(20):
        p=multiprocessing.Process(target=process,args=(i,))
        p.start()

  效果如下:

2、获取自己的CPU核心数量及目前所有运行的进程

  • cpu_count () 方法获取当前机器的 CPU 核心数量
  • active_children () 方法得到目前所有的运行的进程。
import multiprocessing
import time

def process(num):
    time.sleep(num)
    print("process:",num)

if __name__ == '__main__':
    for i in range(5):
        p=multiprocessing.Process(target=process,args=(i,))
        p.start()
    print('CPU number:' + str(multiprocessing.cpu_count()))
    for p in multiprocessing.active_children():
        print('Child process name:'+p.name+'id:'+str(p.pid))

    print('Process Ended')

  效果如下:

3、自定义继承Process类

from multiprocessing import Process
import time

class MyProcess(Process):
    def __init__(self,loop):
        Process.__init__(self)
        self.loop=loop
    def run(self):
        for count in range(self.loop):
            time.sleep(1)
            print('pid:'+str(self.pid)+'LoopCount:'+str(count))

if __name__ == '__main__':
    for i in range(2,5):
        p=MyProcess(i)
        p.start()

  效果如下图:

4、daemon属性

  • 设置一个daemon属性,若属性为True,当主线程代码执行结束后,子线程无论是否执行完毕,会自动被终止。
from multiprocessing import Process
import time

class MyProcess(Process):
    def __init__(self,loop):
        Process.__init__(self)
        self.loop=loop
    def run(self):
        for count in range(self.loop):
            time.sleep(1)
            print('pid:'+str(self.pid)+'LoopCount:'+str(count))

if __name__ == '__main__':
    for i in range(2,5):
        p=MyProcess(i)
        p.daemon=True
        p.start()
    print("主进程运行结束!")

  效果如下:

 

5、join()方法

  • join()方法与多线程的使用相同,起着阻塞作用。在这里配合deamon属性一起使用效果更为明显。
from multiprocessing import Process
import time

class MyProcess(Process):
    def __init__(self,loop):
        Process.__init__(self)
        self.loop=loop
    def run(self):
        for count in range(self.loop):
            time.sleep(1)
            print('pid:'+str(self.pid)+'LoopCount:'+str(count))

if __name__ == '__main__':
    for i in range(2,5):
        p=MyProcess(i)
        p.daemon=True
        p.start()
        p.join()
    print("主进程运行结束!")

  会发现所有子线程执行完毕后,主线程代码才会执行。

6、Lock()锁机制

  • Lock()方法,效果同上,其作用是为了让一个进程输出,其他进程等待,等刚才的进程结束,其他进程才能运行,这种现象叫做“互斥”也可以防止所有子进程共同抢占一个全局变量。

from multiprocessing import Process,Lock
import time

class MyProcess(Process):
    def __init__(self,loop,lock):
        Process.__init__(self)
        self.loop=loop
        self.lock=lock
    def run(self):
        for count in range(self.loop):
            time.sleep(0.1)
            # 锁定
            self.lock.acquire()
            print('pid:'+str(self.pid)+'LoopCount:'+str(count))
            # 释放
            self.lock.release()
if __name__ == '__main__':
    lock = Lock()
    for i in range(10,15):
        p=MyProcess(i,lock)
        p.start()
    print("主进程运行结束!")

7、Queue队列

  • 以下代码使用的是生产消费者模式使数据在队列中传递,queue队列同多线程中的使用方法相同,在这里就不多赘述了。
import multiprocessing
from multiprocessing import Process, Semaphore, Lock, Queue
import time
from random import random

class Consumer(Process):
    def __init__(self,queue):
        Process.__init__(self)
        self.queue=queue
    def run(self):
        list1=[]
        while True:
            if self.queue.empty():
                print(list1)
                break
            data= self.queue.get()

            list1.append(data)

class Producer(Process):
    def __init__(self,queue):
        Process.__init__(self)
        self.queue=queue
    def run(self):
        print(11)
        for i in range(10):
            self.queue.put(i)
if __name__ == '__main__':
    queue=multiprocessing.Queue()
    t1=Producer(queue)
    t2=Consumer(queue)
    t1.start()
    t2.start()

 8、Pool进程池

  在进程池的运用中在这里主要讲的有三种方式。

第一,阻塞与非阻塞

  • 非阻塞的含义:不用等到上一个进程结束,可以同时进行多个进程,即并行。
  • 阻塞与非阻塞的含义类似与并发与并行。
  • 运用的函数:pool.apply_async()
from multiprocessing import Lock, Pool
import time


def function(index):
    print( 'Start process: ', index)
    time.sleep(3)
    print( 'End process', index)


if __name__ == '__main__':
    pool = Pool(processes=3)
    for i in range(4):
        # pool.apply_async非阻塞的指定方法
        pool.apply_async(function, (i,))
        # pool.apply 阻塞的指定方法
        # pool.apply(function,(i,))
    print( "Started processes")
    # 关闭进程池
    pool.close()
    pool.join()
    print( "Subprocess done.")

  多个进程同时运行,数据发生资源竞争的现象。

  • 阻塞的含义:在多个进程同时运行时,需要等上一个进程结束,下一个进程才会开始运行,类似与多线程中的并发。
  • 阻塞的函数模块:pool.apply(function,(i,))
from multiprocessing import Lock, Pool
import time


def function(index):
    print( 'Start process: ', index)
    time.sleep(3)
    print( 'End process', index)


if __name__ == '__main__':
    pool = Pool(processes=3)
    for i in range(4):
        # pool.apply_async非阻塞的指定方法
        # pool.apply_async(function, (i,))
        # pool.apply 阻塞的指定方法
        pool.apply(function,(i,))
    print( "Started processes")
    # 关闭进程池
    pool.close()
    pool.join()
    print( "Subprocess done.")

   效果如下:

第二,讲进程作为结果进行返回

from multiprocessing import Lock, Pool
import time

def function(index):
    print( 'Start process: ', index)
    time.sleep(3)
    print( 'End process', index)
    return index

if __name__ == '__main__':
    pool = Pool(processes=3)
    for i in range(4):
        result = pool.apply_async(function, (i,))
        print('result结果显示为:',result.get())
    print( "Started processes")
    pool.close()
    pool.join()
    print( "Subprocess done.")

 

第三,使用map函数进行多网页数据抓取

  • map函数可以遍历每个 URL,然后对其分别执行 scrape 方法。在这里初始化一个 Pool,指定进程数为 3,如果不指定,那么会自动根据 CPU 内核来分配进程数。
  • 数据的解析与保存在这里就先不展开讲了。
from multiprocessing import Pool
import requests
from requests.exceptions import ConnectionError


def scrape(url):
    try:
        print( requests.get(url))
    except ConnectionError:
        print( 'Error Occured ', url)
    finally:
        print( 'URL ', url, ' Scraped')


if __name__ == '__main__':
    pool = Pool(processes=3)
    urls = [
        'https://www.baidu.com',
        'http://www.meituan.com/',
        'http://blog.csdn.net/',
        'http://xxxyxxx.net'
    ]
    pool.map(scrape, urls)

  效果如下:

  关于多线程与多进程及相关知识点的运用介绍到这里就结束了,相信在学习的基础上多加练习一定会有收获...

  本节相关知识的总结来自于崔庆才的多线程与多进程的用法及《看漫画学python》对于多线程与多进程的主要相关知识总结。具体知识点可以浏览一下文章:Python爬虫进阶五之多线程的用法Python爬虫进阶六之多进程的用法

 

标签:__,知识点,self,print,线程,进程,多线程,def
From: https://www.cnblogs.com/LoLong/p/17161916.html

相关文章