python 线程与进程

一,线程与进程的关系

1,线程

线程是进程的基本执行单元,一个进程的所有任务都在线程中执行 进程要想执行任务,必须得有线程,进程至少要有一条线程 程序启动会默认开启一条线程,这条线程被称为主线程或 UI 线程

2,进程

进程是指在系统中正在运行的一个应用程序
每个进程之间是独立的,每个进程均运行在其专用的且受保护的内存

3,进程与线程的区别

  • 根本区别: 进程是操作系统资源分配的基本单位.线程是任务调度和执行的单位
  • 地址区别:进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。
  • 资源区别:进程内的线程共享本进程的资源,但是进程之间的资源是独立的。线程可以看做轻量级进程,同一线程之间,资源共享,每个线程都有独立的运行栈进程切换时,消耗的资源大,效率高。所以涉及到频繁的切换时,使用线程要好于进程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程
  • 执行过程:每个独立的进程有一个程序运行的入口、顺序执行序列和程序入口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

二,线程

当线程对象一但被创建,其活动一定会因调用线程的 start() 方法开始。这会在独立的控制线程调用 run() 方法。

一旦线程活动开始,该线程会被认为是 ‘存活的’ 。当它的 run() 方法终结了(不管是正常的还是抛出未被处理的异常),就不是’存活的’。 is_alive() 方法用于检查线程是否存活。

其他线程可以调用一个线程的 join() 方法。这会阻塞调用该方法的线程,直到被调用 join() 方法的线程终结。

线程有名字。名字可以传递给构造函数,也可以通过 name 属性读取或者修改。

1,基本使用

通过函数来使用

import time
from threading import Thread
 
num = 0
def text(name):
    for i in range(2):
        print('%s running' % name)
        time.sleep(1)
        global num
        num += 1
        print('num :%d' % num)
 
 
if __name__ == '__main__':
    t1 = Thread(target=text, args=('bai',))
    t2 = Thread(target=text, args=('root',))
    t1.start()
    t2.start()

在这里插入图片描述

2,类的使用

#!/usr/bin/env python 
# -*- coding:utf-8 -*-
import threading
import time
import os

exitFlag = 0

class myThread (threading.Thread):
    def __init__(self, threadID, name, counter,*args, **kwargs):
        super(myThread,self).__init__(*args, **kwargs)#首先需要先保留原来threading.Thread中的初始化函
        self.threadID = threadID#重命名线程的ID
        self.name = name #线程的名字
        self.counter = counter #线程的数量

    def run(self):#代表线程活动的方法。
        print ("开始线程:" + self.name)
        print('开启完成')
        print_time(self.name, self.counter, 3)
        print ("退出线程:" + self.name)

def print_time(threadName, delay, counter):
    while counter:
        if exitFlag:
            threadName.exit()
        time.sleep(delay)
        print ("%s: %s" % (threadName, time.ctime(time.time())))
        counter -= 1
start = time.time()


thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(1, "Thread-2", 1)
print(threading.enumerate(),'返回一个包含正在运行的线程的list')

# 开启新线程
thread1.start()
thread2.start()

#等待线程只结束,阻塞到结束
#thread1.join() 
#thread2.join()


end = time.time()
print(threading.enumerate(),'返回一个包含正在运行的线程的list')
print ("退出主线程")
print('总耗时间:',end - start)

在这里插入图片描述
阻塞后的
在这里插入图片描述

3,资源竞争

我们把run修改为

def run(self):#代表线程活动的方法。
        print ("开始线程:" + self.name)
        for i in range(10000000):
            global num
            num += 1
        print(num)
        print ("退出线程:" + self.name)

这个结果是不是意料之外的,当数据量过大时
在这里插入图片描述

三,进程

1,multiprocessing模块

python中的多线程无法利用多核优势,如果想要充分地使用多核CPU的资源(os.cpu_count()查看),在python中大部分情况需要使用多进程。Python提供了multiprocessing。
multiprocessing 是一个支持使用与 threading 模块类似的 API 来产生进程的包。 multiprocessing 包同时提供了本地和远程并发操作,通过使用子进程而非线程有效地绕过了 全局解释器锁。 因此,multiprocessing 模块允许程序员充分利用给定机器上的多个处理器。 它在 Unix 和 Windows 上均可运行。
调用方法和线程类似

2,Process类的介绍

创建进程的类:

Process([group [, target [, name [, args [, kwargs]]]]]),由该类实例化得到的对象,表示一个子进程中的任务(尚未启动) 强调: 1. 需要使用关键字的方式来指定参数 2. args指定的为传给target函数的位置参数,是一个元组形式,必须有逗号

使用方法

from multiprocessing import Process  # 可以开启发起子进程调用
import time

 
def task(name):
    print('%s is running' % name)
    time.sleep(1)
    print('%s is done' % name)
 
 
if __name__ == '__main__':
    # Process(target=task,kwargs={'name':'子进程1'})  # kwargs可以按照字典的方式传参数,args按照位置的方式传参数
    p = Process(target=task, args=('子进程1',))  
    p.start()  
 
    print('主')

#部分源代码

class multiprocessing.Process(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)
进程对象表示在单独进程中运行的活动。 Process 类拥有和 threading.Thread 等价的大部分方法。

应始终使用关键字参数调用构造函数。 group 应该始终是 None ;它仅用于兼容 threading.Thread 。 target 是由 run() 方法调用的可调用对象。它默认为 None ,意味着什么都没有被调用。 name 是进程名称(有关详细信息,请参阅 name )。 args 是目标调用的参数元组。 kwargs 是目标调用的关键字参数字典。如果提供,则键参数 daemon 将进程 daemon 标志设置为 TrueFalse 。如果是 None (默认值),则该标志将从创建的进程继承。

默认情况下,不会将任何参数传递给 target 。

如果子类重写构造函数,它必须确保它在对进程执行任何其他操作之前调用基类构造函数( Process.__init__() )。

在 3.3 版更改: 加入 daemon 参数。

run()
表示进程活动的方法。

你可以在子类中重载此方法。标准 run() 方法调用传递给对象构造函数的可调用对象作为目标参数(如果有),分别从 args 和 kwargs 参数中获取顺序和关键字参数。

start()
启动进程活动。

这个方法每个进程对象最多只能调用一次。它会将对象的 run() 方法安排在一个单独的进程中调用。

join([timeout])
如果可选参数 timeout 是 None (默认值),则该方法将阻塞,直到调用 join() 方法的进程终止。如果 timeout 是一个正数,它最多会阻塞 timeout 秒。请注意,如果进程终止或方法超时,则该方法返回 None 。检查进程的 exitcode 以确定它是否终止。

一个进程可以被 join 多次。

进程无法join自身,因为这会导致死锁。尝试在启动进程之前join进程是错误的。
from multiprocessing import Process
import time
class MyProcess(Process):
    def __init__(self, name):
        super().__init__()  # 将父类的功能进行重用
        self.name = name
 
    def run(self):  # 这里一定要用run,下面start将调用这个run
        print('%s is running' % self.name)
        time.sleep(3)
        print('%s is done' % self.name)
 
if __name__ == '__main__':
    p1 = MyProcess('子进程1')
    p2 = MyProcess('子进程2')
    p3 = MyProcess('子进程3')
    p1.start()  # 触发上面的run方法
    p2.start()
    p3.start()
	 # 这三个仍然是并发执行,只是等待最长的程序执行完才结束
    p1.join()
    p2.join()
    p3.join()
    print('主进程结束')

进程之间不存在先后顺序
在这里插入图片描述

4,GIL全局解释器锁

GIL(Global Interpreter Lock)在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势
GIL本质就是一把互斥锁,既然是互斥锁,所有互斥锁的本质都一样,都是将并发运行变成串行,以此来控制同一时间内共享数据只能被一个任务所修改,进而保证数据安全。
在一个python的进程内,不仅有test.py的主线程或者由该主线程开启的其他线程,还有解释器开启的垃圾回收等解释器级别的线程,总之,所有线程都运行在这一个进程内,毫无疑问

1、所有数据都是共享的,这其中,代码作为一种数据也是被所有线程共享的(test.py的所有代码以及Cpython解释器的所有代码) 例如:test.py定义一个函数work(代码内容如下图),在进程内所有线程都能访问到work的代码,于是我们可以开启三个线程然后target都指向该代码,能访问到意味着就是可以执行。

2、所有线程的任务,都需要将任务的代码当做参数传给解释器的代码去执行,即所有的线程要想运行自己的任务,首先需要解决的是能够访问到解释器的代码。

综上:

如果多个线程的target=work,那么执行流程是

多个线程先访问到解释器的代码,即拿到执行权限,然后将target的代码交给解释器的代码去执行

解释器的代码是所有线程共享的,所以垃圾回收线程也可能访问到解释器的代码而去执行,这就导致了一个问题:对于同一个数据100,可能线程1执行x=100的同时,而垃圾回收执行的是回收100的操作,解决这种问题没有什么高明的方法,就是加锁处理,如下图的GIL,保证python解释器同一时间只能执行一个任务的代码

num = 0
class myThread (threading.Thread):
    def __init__(self, threadID, name, counter,*args, **kwargs):
        super(myThread,self).__init__(*args, **kwargs)#首先需要先保留原来threading.Thread中的初始化函
        self.threadID = threadID#重命名线程的ID
        self.name = name #线程的名字
        self.counter = counter #线程的数量
        self.num = 0

    def run(self):#代表线程活动的方法。 
        print ("开始线程:" + self.name)
        threadLock.acquire() #上锁
        for i in range(10000000):
            global num
            num += 1
        threadLock.release() #解锁
        print(num)
        print ("退出线程:" + self.name)


start = time.time()
threadLock = Lock()#获取锁
# 创建新线程
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(1, "Thread-2", 1)
thread3 = myThread(3, "Thread-3", 3)


# 开启新线程
thread1.start()
thread2.start()
# thread3.start()
print(threading.enumerate(),'返回一个包含正在运行的线程的list')


#等待线程只结束
thread1.join()
thread2.join()
# thread3.join()

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值