python多线程和多进程

Table of Contents

  1. 线程概念
    1. 进程与线程的区别
      1. 线程常用方法
    2. 线程常用方法
      1. 普通创建方式
      2. 重写类,来实现启动线程
      3. 其他方法
    3. GIL
      1. python多线程的工作流程
    4. 线程锁
    5. 互斥锁(mutex)
    6. 递归锁
    7. 信号量(BoundedSemphore)
    8. Event事件
    9. 条件(Condition类)
    10. 定时器
  2. 多进程
    1. 什么是多进程
    2. 相关用法
    3. 进程间的通信
      1. 使用Queue
      2. 使用Pipe
    4. 进程池

多线程

线程概念

所谓线程是操作系统能够进行运算调度的最小单元,一个线程是一个执行上下文,是cpu执行命令时的一串指令,一个进程能够并发出多个线程,来执行不同的任务。

进程与线程的区别

1.同一进程中的线程共享同一内存空间,但进程之间是独立的。
2.同一进程中的线程共享数据,但是进程之间的数据是独立的。
3.同一进程中的线程可以很方便地通信,但是进程之间通信之间的通信则需要通过代理实现。
4.在一个进程中创建一个线程很容易,但是进程创建一个子进程,则fork一个出来,复制一份内存空间
5.主线程可以操纵同一进程中的其他线程,但是进程只能操纵它的子进程。线程启动快,进程启动慢。线程之间切换,比较耗时,比较占用cpu,比较适合io密集型任务,
而进程之间的切换比较快速,但是进程fork出很多子进程,比较占用内存空间。对主线程的修改可能会对其他线程造成影响,但是进程和进程,进程和子进程,都是一
个独立的内存空间,所以一个进程的修改,不会对其他进程造成影响。

线程常用方法



































方法 注释
start() 线程如果已经准备,启用该方法,则开始启动线程
setName() 为线程设置名称
join() 逐个执行每个进程,主线程会等到子线程全部执行完毕,在退出
run() 可以重写类时,重写run方法,起到自定义线程类的作用
setDaemon(True) 设置为守护进程

线程常用方法

普通创建方式

import threading
import time
def run(n):
        print("task", n)
        sleep(1)

t1 = threading.Thread(target=run, args=('t1',))
t2 = threading.Thread(target=run, args=('t2',))
t3 = threading.Thread(target=run, args=('t3',))

t1.start()
t2.start()
t3.start()
t1.join()
t2.join()
t3.join()

重写类,来实现启动线程

class MyThread(threading.Thread):
        def __init__(self, n):
                super().__init__()
                self.n = n

        def run(self, self.n):
                print("task", n)
                time.sleep(1)
                print("1s")
                time.sleep(1)
                print("2s")

if __name__ == "__main__":
        t1 = MyThread("one")
        t2 = MyThread("two")
        t1.start()
        t2.start()

其他方法

  • threading.currentthread(): 输出当前线程的名称
  • threading.activecount(): 输出当前活跃线程的个数
  • t.setDaemon(True): 设置当前线程为守护进程,只要主进程一退出,子进程也随之退出, 必须在调用start()之前使用,否则会报RuntimeError的错误

GIL

1.GIL全称为Global Interpreter Lock(全局解释器锁),全局解释器锁的存在,保证了同一进程中,只能有一个线程正在运行,所以python多线程编程效率并没有那么高,
通过使用多进程的方式,能够有效地提升效率。python2中GIL是通过ticks来计数,计数值到达100,则解释器,就会释放,但是在python3中,GIL是通过计时来实现了,只要时间一到,不管任务完没完成,都会收回
GIL(这种情况下,对cpu密集型运算更有利了一些,但依然问题很大),这种情况下,python对cpu密集型任务并不友好,而对io密集型任务比较友好。
2.GIL在cpython才存在,jpython和pypy中是没用GIL,因为cpython调用的是c语言的原生线程

python多线程的工作流程

ipython使用多线程的时候,是使用c语言原生线程
1.拿到公共数据
2.申请GIL
3.python解释器调用os原生线程
4.os执行cpu运算
5.时间到了收回GIL,无论是否完成任务
6.如此循环以上过程
7.当其他线程完成后,就继续执行之前的线程(从之前线程执行的上下文处开始执行),每个线程都执行自己的运算,时间到了就切换线程

线程锁

因为线程之间是公用同一份数据,所以当多个线程对同一数据进行操作时,容易产生脏数据,导致数据出现错误,所以出现了线程锁的概念,在一个线程拥有该锁时,其他线程不能进行操作,保证了,同一数据在同一时刻,
只有一个线程对其进行操作

互斥锁(mutex)

为了防止上述情况的发生,所以有了互斥锁

import threading
# 实例化了一个锁对象
lock = threading.Lock()
def run():
        lock.acquire()  # 获得锁,其他线程不能操作
        print("hello world")
        lock.release()  # 释放锁,其他线程开始竞争

递归锁

与上面的Lock用法类似,RLock可以嵌套,实现多个锁

import threading
rlock = threading.RLock()
def run():
        rlock.acquire()
        rlock.acquire()
        print("hello world")
        rlock.release()
        print("hello syk")
        rlock.release()

信号量(BoundedSemphore)

互斥锁只允许一个线程修改数据,信号量则允许多个线程同时修改,就比如厕所有三个坑,只有其中的坑空出来了,才能有人进去

import threading
semphore = threading.BoundedSemphore(5)  # 表示最多有五个线程同时进行操作
def run(i):
        semphore.acquire()
        print("hello",i)

for i in range(20):
        t = threading.Thread(target=run)
        t.start()

while t.active_count != 1:
        pass
else:
        print("all thread has been done")

Event事件

python线程的事件主要是用于主线程控制其他线程的执行,event是一个简单的事件同步对象,提供了一下几种方法:






























方法 注释
set() 将flag置为True
clear() 将flag置为False
isset() 是否将flag设置为True
wait() 一直监听flag,如果没有监听到,则一直为阻塞状态

条件(Condition类)

使得线程等待,满足条件才将n个线程释放

定时器

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

import threading
def hello():
        print("hello world")

t = threading.Timer(1, hello)
t.start()

多进程

什么是多进程

1.一个程序的执行实例就是一个进程,进程提供一个程序运行所需要的所有资源(进程本质上是资源的集合)
2.每一个进程都有自己的虚拟地址空间,可执行的代码块,操作系统接口,安全的上下文(记录启动该进程的用户和权限),还有唯一的进程ID,
环境变量,优先级,最大最小的工作空间(内存空间)。

与进程相关的资源:
1.内存页(统一进程下的线程共享同一内存空间)
2.文件描述符
3.安全凭证(启动该进程的用户ID)

相关用法

from multiprocessing import Process
import time


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


if __name__ == "__main__":
     p = Process(target=hello)
     p.start()
     p.join()

进程间的通信

由于进程间的数据是独立的,所以进程间的通信可以通过Queue和Pipe来实现

使用Queue

from  multiprocessing import Process, Queue
def q(p):
        p.put([41, None, 'hello'])

if __name__ == "__main__":
     p = Queue()
     t = Process(target=q, args=(p,))
     t.start()
     print(q.get())
     t.join()

使用Pipe

Pipe的实质是数据的传递而不是数据的共享,在管道的两头都有一个recv和send方法,负责接受和发送,
但是如果两端同时进行相同的操作,那么就会发生错误,破坏管道中的数据。

from multiprocessing import Process, Pipe
import time


def parent_pro(conn):
        conn.send("hello world, my son")
        conn.close()


def child_pro(conn):
        data = conn.recv()
        print(data)
        conn.close()


if __name__ == "__main__":

        parent_pipe, child_pipe = Pipe()
        p1 = Process(target=parent_pro, args=(parent_pipe,))
        p2 = Process(target=child_pro, args=(child_pipe,))
        p1.start()
        p2.start()
        p1.join()
        p2.join()

进程池

因为多进程比较消耗内存空间,所以在使用多进程时,一般可以使用进程池(用来防止内存被大量消耗), (多线程不需要这种概念,多线程的启动消耗比较小,但是多线程切换比较消耗cpu资源)

常用方法:
1.apply(): 同步执行(串行)
2.applyasync(): 异步执行(并行)
3.terminate(): 立即关闭进程池
4.join(): 主进程等待所有子进程执行完毕,必须在close()和terminate()之后
5.close(): 待所有进程执行完毕之后,关闭进程池

你可能感兴趣的:(python多线程和多进程)