多进程与多线程

一 什么是多线程与多进程

  1. 什么是线程
    线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务,一个线程即cpu需要执行的一条指令
  2. 什么是进程
    一个程序的执行实例就是一个进程。每一个进程提供执行程序所需的所有资源。(进程本质上是资源的集合)
    一个进程有一个虚拟的地址空间、可执行的代码、操作系统的接口、安全的上下文(记录启动该进程的用户和权限等等)、唯一的进程ID、环境变量、优先级类、最小和最大的工作空间(内存空间),还要有至少一个线程。
    每一个进程启动时都会最先产生一个线程,即主线程。然后主线程会再创建其他的子线程
  3. 进程与线程的区别
    1) 同一个进程中的线程共享同一内存空间,但是进程之间是独立的。
    2) 同一个进程中的所有线程的数据是共享的(进程通讯),进程之间的数据是独立的。
    3) 对主线程的修改可能会影响其他线程的行为,但是父进程的修改(除了删除以外)不会影响其他子进程。
    4) 线程是一个上下文的执行指令,而进程则是与运算相关的一簇资源。
    5) 同一个进程的线程之间可以直接通信,但是进程之间的交流需要借助中间代理来实现。
    6) 创建新的线程很容易,但是创建新的进程需要对父进程做一次复制。
    7) 一个线程可以操作同一进程的其他线程,但是进程只能操作其子进程。
    8) 线程启动速度快,进程启动速度慢(但是两者运行速度没有可比性)

二 多线程

  1. 创建线程
    在python中有专门的模块来创建线程,有(_thread和threading)这两个模块,_thread是低级模块,threading是高级模块,对 _thread进行了封装,绝大多数情况下,我们只需要使用 threading 这个高级模块
    现在来创建一个线程:其实质就是把一个函数传入并创建 Thread实例,然后调用 start()开始执行
    1> 这是创建线程的第一种方法:
import threading

def job():
    print('这是一个需要执行的任务')
    #激活的线程个数
    print('当前的线程个数:',threading.active_count())
    #打印当前线程的详细信息
    print('当前线程信息:',threading.current_thread())

if __name__=='__main__':
    t1=threading.Thread(target=job)
    t1.start()

在这里插入图片描述
通过实例化对象来创建一个线程

def __init__(self, group=None, target=None, name=None,
                 args=(), kwargs=None, *, daemon=None)

1)在创建线程的时候,需要传的参数,其中target为函数名,name为定义的名称,如果不传值的话,默认为Thread-1,args为需要传入的参数,当然函数本身不需要传的话,这里也就不需要
2)threading模块中 active_count()是用来统计正在活跃的线程数的,由于任何进程默认就会启动一个线程,我们把该线程称为主线程,主线程又可以启动新的线程,因此当前的线程数为2
3)而current_thread()是用来显示当前线程的详细信息的,里面包含名称和启动的时间

2> 这是创建线程的第二种方法:
通过继承threading.Thread来自定义线程类,其本质是:重构Thread类中的run方法

import threading

#类的继承
class IpThread(threading.Thread):
    #重写构造方法
    def __init__(self,jobname):
        super(IpThread,self).__init__()
        self.jobname=jobname
    #将多线程需要执行的任务重写到run方法中
    def run(self):
        print('正义')
t1=IpThread(jobname='new_job')
t1.start()

在这里插入图片描述
如果你要在run方法中传递参数的话,那就将参数通过构造函数与self绑定在一起

  1. 多线程中的join方法
    1) 不使用多线程
import time

#不使用线程
def music(name):
    for i in range(2):
        print('正在听音乐: %s' %(name))
        time.sleep(2)
def code(name):
    for i in range(2):
        print('正在写代码: %s' %(name))
        time.sleep(3)
if __name__=='__main__':
    start_time=time.time()
    music('神话')
    code('爬虫')
    print('花费的时间: %s' %(time.time()-start_time))

在这里插入图片描述
事情是一个一个的做,没有同时进行,但是我敲代码的时候是会听歌的,
因此此处可以使用多线程来处理的

2) 使用多线程

import threading
import time

#使用线程
def music(name):
    for i in range(2):
        print('正在听音乐: %s' %(name))
        time.sleep(2)
def code(name):
    for i in range(2):
        print('正在写代码: %s' %(name))
        time.sleep(3)
if __name__=='__main__':
    start_time=time.time()
    t1=threading.Thread(target=music,args=('神话',))
    t2=threading.Thread(target=code,args=('爬虫',))
    t1.start()
    t2.start()
    print('花费的时间: %s' %(time.time()-start_time))

在这里插入图片描述
发现时间确实是变快了好多,但是感觉跟想象的不太一样,执行时间都结束了,而事情却没做完,这是因为主线程阻塞了子线程进行
这里就需要引入join方法了

3) 引入join方法

import threading
import time

#使用线程
def music(name):
    for i in range(2):
        print('正在听音乐: %s' %(name))
        time.sleep(2)
def code(name):
    for i in range(2):
        print('正在写代码: %s' %(name))
        time.sleep(3)
if __name__=='__main__':
    start_time=time.time()
    t1=threading.Thread(target=music,args=('神话',))
    t2=threading.Thread(target=code,args=('爬虫',))

    t1.start()
    t2.start()
    #等待所有的子线程执行结束之后,继续执行主线程的内容
    t1.join()
    t2.join()
    print('花费的时间: %s' %(time.time()-start_time))

在这里插入图片描述
现在的执行时间是正确的了
其实join的本质就是:等待所有的子线程执行结束之后,再去执行主线程的内容

  1. 守护线程setDaemon方法
    守护线程的意义:为了让主线程在执行结束时,强制结束未执行完的子线程
import threading
import time

#使用线程,让其主线程执行结束后,强制结束所有的子线程
def music(name):
    for i in range(2):
        print('正在听音乐: %s' %(name))
        time.sleep(2)
def code(name):
    for i in range(2):
        print('正在写代码: %s' %(name))
        time.sleep(3)
if __name__=='__main__':
    start_time=time.time()
    t1=threading.Thread(target=music,args=('神话',))
    t2=threading.Thread(target=code,args=('爬虫',))
    #将子线程声明为守护线程,如果设置为True,子线程启动后,当主线程执行结束,子线程也结束
    #设置setDaemon必须在线程启动前
    t1.setDaemon(True)
    t2.setDaemon(True)
    t1.start()
    t2.start()

    print('花费的时间: %s' %(time.time()-start_time))

在这里插入图片描述

可以看到主线程在结束时,未完成的子线程也结束了

  1. 线程锁lock
    多线程中,所有变量都由所有线程共享,所以,任何一个变量都可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱
    下面来看一个实例:
import threading


def add():
    global money
    for i in range(1000000):
        money+=1
def reduce():
    global money
    for i in range(1000000):
        money-=1
if __name__ == '__main__':
    money=0
    t1=threading.Thread(target=add)
    t2=threading.Thread(target=reduce)
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print('最终金额: %s'%(money))

在这里插入图片描述
理论上money的值应该为0,但是,由于线程的调度是由操作系统决定的,当t1、t2交替执行时,只要循环次数足够多,money的结果就不一定是0了
原因是因为:高级语言的一条语句在CPU执行时是若干条语句,即使一个简单的计算:money+=1 也是分为两步去完成的 先计算 money+1,将结果存入临时变量,再将临时变量赋值给money,而两个线程都拥有各自的临时变量,当t1与t2交替执行的时候,就会导致把同一个对象给改的混乱
这里我们为了避免这样的情况发生,从而引入了线程锁lock 这个概念

def add(lock):
    # 操作变量之前进行加锁
    lock.acquire()
    global money
    for i in range(1000000):
        money+=1
    # 操作变量之后进行解锁
    lock.release()
def reduce(lock):
    lock.acquire()
    global money
    for i in range(1000000):
        money-=1
    lock.release()
if __name__ == '__main__':
    money=0
    # 实例化一个锁对象
    lock=threading.Lock()
    t1=threading.Thread(target=add,args=(lock,))
    t2=threading.Thread(target=reduce,args=(lock,))
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print('最终金额: %s'%(money))

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值