python学习日记14 python基本语法(进程与线程)

一、进程和线程

1.概念

1.1多任务

程序的运行是CPU和内存协同工作的结果

操作系统比如Mac OS X,UNIX,Linux,Windows等,都是支持“多任务”的操作系统

问题1:什么是多任务?

​ 就是操作系统可以同时运行多个任务。打个比方,你一边在用浏览器上网,一边在听MP3,一边在用Word赶作业,这就是多任务,至少同时有3个任务正在运行。还有很多任务悄悄地在后台同时运行着,只是桌面上没有显示而已

问题2:多核CPU已经非常普及了,但是,即使过去的单核CPU,也可以执行多任务。由于CPU执行代码都是顺序执行的,那么,单核CPU是怎么执行多任务的呢?

​ 答案:操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再切换到任务3,执行0.01秒……这样反复执行下去。实际上,每个任务都是交替执行的,但是,表面上看,由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样【现在的电脑最起码都4核起】

并行:真正一起执行,任务数量小于cpu的核心数量【理想型】

并发:看上去一起执行,任务数量大于cpu的核心数量【现实型】

​ 多核cpu实现多任务的原理:真正的并行执行多任务只能在多核cpu上实现,但是由于任务数量远远多于cpu的核心数量,所以操作系统也会自动把很多任务轮流调度到每个核心上执行

对于操作系统而言,一个任务就是一个进程【process】,一个操作系统可以开启多个进程

有的进程启动之后,有可能又需要处理多个子任务,这些子任务就是线程【thread】

1.2进程

是一个程序的运行状态和资源占用(内存,CPU)的描述

进程是程序的一个动态过程,它指的是从代码加载到执行完毕的一个完成过程

进程的特点:

​ a.独立性:不同的进程之间是独立的,相互之间资源不共享(举例:两个正在上课的教室有各自的财产,相互之间不共享)

​ b.动态性:进程在系统中不是静止不动的,而是在系统中一直活动的(举例:教室里一直在讲课)

​ c.并发性:多个进程可以在单个处理器上同时进行,且互不影响

多进程:一个操作系统可以运行多个应用程序

1.3线程

​ 线程是进程的组成部分,一个进程可以有多个线程,每个线程去处理一个特定的子任务

​ 线程的执行是抢占式的,多个线程在同一个进程中可以并发执行,其实就是CPU快速的在不同的线程之间切换,也就是说,当前运行的线程在任何时候都有可能被挂起,以便另外一个线程可以运行

​ 例如:打开酷狗音乐 ————》这是一个进程

​ 播放歌曲和刷新歌词————》这是两个线程(两个线程在同时交替执行,所以说是并发的)

​ 一个进程中的不同线程之间是资源共享的

​ 多线程:

​ 在一个进程中,多个线程可以同时进行

​ 多线程是实现了并发机制的一种有效手段,一个进程中可以包含多个线程,不同线程之间是可以资源共享的,同时运行可以提高程序的执行效率,可以同时完成多个工作

​ 应用:一个浏览器可以同时下载多张图片和多个视频

​ 一个服务器可以同时响应多个用户请求

进程和线程之间的区别和联系

​ a.一个程序启动之后,肯定会启动一个进程

​ b.一个进程启动之后,可能会启动多个线程,但是,该进程至少需要一个线程,否则这个进程是没有意义的

​ c.多个进程之间资源不共享,多个线程之间资源共享

​ d.系统创建进程需要为该进程重新分配系统资源,而创建线程容易很多,所以在实际开发中,使用多线程多于多进程

二、进程

1.单任务现象

# 任务2
def func():
    print("11111111")


# 任务1
if __name__ == '__main__':
    while True:
        print("main~~~")
# 永远执行不到下面
    func()

2.多进程实现多任务

2.1启动进程实现多任务

Unix/Linux操作系统提供了一个fork()系统调用,fork()存在于os模块下【os.fork()】,但是,Windows没有fork调用

由于Python是跨平台的,提供了一个跨平台的多进程支持。multiprocessing模块就是跨平台版本的多进程模块

multiprocessing模块提供了一个Process类来代表一个进程对象

# 1.导入模块
from multiprocessing import  Process
import os,time


# 子进程的任务
def func():
    # os.getppid()表示获取当前进程的父进程的进程号
    print(f"子进程启动,进程号:%s,对应的父进程为:%s" % (os.getpid(),os.getppid()))
    while True:
        print("11111111")
        time.sleep(0.5)

def func1(a,b):
    print(a + b)
    print(f"子进程启动,进程号:%s,对应的父进程为:%s" % (os.getpid(),os.getppid()))
    while True:
        print("11111111")
        time.sleep(0.5)

if __name__ == '__main__':
    # 2.在__main__中执行的代码表示主进程/父进程
    # 注意:一个程序启动,则表示一个父进程启动,系统会自动给该进程分配一个进程号,通过os.getpid()获取进程号
    print(f"父进程启动,进程号:%s" % (os.getpid()))

    # 3.在父进程中创建一个子进程
    """
    Process(target,args)
        target:当前进程需要执行的任务的函数名
        args:表示任务函数的参数,值一定是一个元组
    """
    # 注意:进程对象创建完毕之后,一定要手动启动
    # a.任务函数没有参数
    # p = Process(target=func)
    # p.start()

    # b.任务函数有参数
    p = Process(target=func1,args=(23,10))
    p.start()

    # 主进程的任务
    while True:
        print("main~~~")
        time.sleep(0.5)

2.2父子进程的先后顺序

from  multiprocessing import  Process
import  os,time

def run():
    print(f"子进程启动:%s" % (os.getpid()))
    print('112431513531')
    time.sleep(5)
    print(f"子进程结束~~~~:%s" % (os.getpid()))

if __name__ == '__main__':
    print(f"父进程启动:%s" % (os.getpid()))

    p = Process(target=run)
    p.start()

    # 合并进程【将指定的子进程插队,会优先执行合并进程】
    """
    在默认情况下,根据系统的调度,主进程有可能会早于子进程结束
    但是当将子进程进行合并【join】之后,主进程会等待子进程执行完毕之后才结束
    """
    p.join()

    print('main~~~~~~')
    time.sleep(0.5)

    print(f"父进程结束~~~~~:%s" % (os.getpid()))

"""
join之前
    父进程启动:63157
    main~~~~~~
    子进程启动:63158
    112431513531
    父进程结束~~~~~:63157
    子进程结束~~~~:63158

join之后:
    父进程启动:63138
    子进程启动:63139
    112431513531
    子进程结束~~~~:63139
    main~~~~~~
    父进程结束~~~~~:63138
"""

2.3多个进程中的全局变量

from  multiprocessing import  Process
import  os,time

# 需求:在子进程中修改全局变量,查看父进程中访问到的全局变量的值

num = 100

def run():
    print(f"子进程启动~~~~")
    global  num
    num += 50
    print(f"子进程结束~~~~:{num}")   # 150

if __name__ == '__main__':
    print(f"父进程启动:%s" % (os.getpid()))

    p = Process(target=run)
    p.start()

    # 优先执行子进程
    p.join()

    print(f"父进程结束~~~~~:%s" % (os.getpid()))
    print(f"全局变量num={num}")   # 100


"""
总结:
    验证了进程之间是独立的,相互之间资源不共享
    
    工作原理:在创建子进程对象的时候,会将所有全局数据进行备份,
            每个进程都有自己的代码段,数据段和堆栈段,所以进程之间是独立的,资源不共享
"""

2.4启动大量子进程

如果要启动大量的子进程,可以用进程池的方式批量创建子进程

from  multiprocessing import  Process,Pool
import  os,time

def func(n):
    print(f"子进程{n}启动")
    print("111111")
    time.sleep(2)
    print(f"子进程{n}结束~~~~~")

if __name__ == '__main__':
    print(f"父进程启动")

    # 创建多个子进程
    # 方式一:Process
    # p1 = Process(target=func)
    # p1.start()
    # p2 = Process(target=func)
    # p2.start()
    # p3 = Process(target=func)
    # p3.start()

    # 方式二:Pool
    # 创建进程池的对象
    # Pool(num):num表示可以同时执行的进程的数量,num省略,则默认值为当前操作系统的cpu核心数量
    pool = Pool(2)

    # 创建多个进程对象,通过进程池统一管理
    for i in range(10):
        pool.apply_async(func,args=(i,))

    # 当子进程添加完毕后,需要关闭进程池,调用close之后表示向进程池中不能继续添加新的进程对象
    pool.close()

    # 使用进程池的时候,建议调用join,在此处会阻塞主进程,
    # 当进程池中的所有子进程全部执行完毕,才会继续执行主进程中的任务
    pool.join()

    print(f"父进程结束~~~~")

2.5进程之间的通信

Process之间是需要通信的,操作系统提供了很多机制来实现进程间的通信,Python的multiprocessing模块包装了底层的机制,提供了Queue、Pipes等多种方式来交换数据

需求;在父进程中创建两个子进程,一个往Queue里写数据,一个从Queue里读数据

# 进程之间需要通信,无法直接通信,所以可以通过一个队列进行通信
from multiprocessing import  Process,Queue
from time import  sleep

# 需求:在父进程中创建两个子进程,一个进程向队列中写入数据,另一个进程从队列中获取数据
def write(queue):
    print("写数据的进程启动")
    for ch in "ABC":
        print(f"put {ch} to queue")
        # 向队列中添加数据
        queue.put(ch)
        sleep(1)
    print("写数据的进程结束~~~~")

def read(queue):
    print("读数据的进程启动")
    for _ in range(3):
        # 从队列中获取数据
        value = queue.get(True)
        print(f"get {value} from queue")
    print("读数据的进程结束~~~~")

if __name__ == '__main__':
    print("父进程启动")
    # 创建一个队列的对象
    queue = Queue()
    # 创建进程的对象
    write_process = Process(target=write,args=(queue,))
    read_process = Process(target=read,args=(queue,))

    write_process.start()
    read_process.start()

    write_process.join()
    read_process.join()

    print("父进程结束~~~")

三、线程

多任务可以由多进程完成,也可以由一个进程内的多线程完成

在一个进程的内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”叫做线程

线程通常叫做轻型的进程。线程是共享内存空间的并发执行的多任务,每一个线程都共享一个进程中的资源

线程是最小的执行单元,而进程由至少一个线程组成。如何调度进程和线程,完全由操作系统决定,程序自己不能决定什么时候执行,也不能决定执行多长时间

而且线程是操作系统直接支持的执行单元,因此,高级语言通常都内置多线程的支持,Python也不例外,并且,Python的线程是真正的Posix Thread,而不是模拟出来的线程

模块

1、_thread模块:提供了低级别的、原始的线程【低级并不是不好,只是功能比较有限,底层采用的是c语言封装的】

2、threading模块:高级模块,对_thre
ad进行了封装,并提供了_thread中没有的功能

绝大多数情况下,我们只需要使用threading这个高级模块

1.创建线程

创建并启动一个线程就是把一个函数传入并创建Thread实例,然后调用start()开始执行

"""
通过进程可以执行多任务,通过线程也可以执行多任务

创建进程:p = Process(target=xxx,args=(xx,xx..))   p.start()    进程号
创建线程:t = Thread(target=xxx,args=(xx,xx..))    t.start()    线程名称
"""

# 1.导入模块
# import  multiprocessing  # 进程模块
import  threading,os,time       # 线程模块

def run():
    print(f"子线程启动:{threading.current_thread().name}")
    time.sleep(2)
    print(f"子线程结束~~~~:{threading.current_thread().name}")

def run1(num1,num2):
    print(num1,num2)
    print(f"子线程启动:{threading.current_thread().name}")
    time.sleep(2)
    print(f"子线程结束~~~~:{threading.current_thread().name}")

if __name__ == '__main__':
    # 2.任何一个程序启动之后,会启动一个进程,
    # 该进程被称为主进程,该进程会默认启动一个线程,被称为主线程
    # threading.current_thread().name获取当前正在执行的线程的名称,主线程的名称为MainThread
    print(f"主进程{os.getpid()}中的主线程:{threading.current_thread().name}")

    # 3.创建子线程
    """
    Thread(target,args,name)
        target:当前线程需要执行的任务的函数名
        args:表示任务函数的参数,值一定是一个元组
        name:给线程命名
    """
    # 注意:子线程默认的名称为Thread-1,Thread-2.。。。
    # a.任务函数无参
    # t = threading.Thread(target=run,name='hello')
    # t.start()

    # b.任务函数有参
    t = threading.Thread(target=run1, name='hello',args=(34,6))
    t.start()

    # 和进程的用法类似,也可以合并线程,则该线程优先执行
    t.join()

    print(f"主进程{os.getpid()}中的主线程结束~~~~:{threading.current_thread().name}")

2.线程中的全局变量

import  threading

# 假设银行卡余额
balance = 0

def change(n):
    global  balance
    balance += n
    balance -= n     # 0

def run(m):
    for _ in range(1000000):
        change(m)

if __name__ == '__main__':
    t1 = threading.Thread(target=run,args=(5,))
    t2 = threading.Thread(target=run, args=(8,))
    t1.start()
    t2.start()

    t1.join()
    t2.join()

    print(f"balance={balance}")   # 理论上结果为0


"""
多个进程之间是独立的,资源不共享,所以当一个进程修改全局变量,另一个进程访问到的结果没有影响
多个线程之间是资源共享的,所以当多个线程同时访问一个全局变量的时候,会导致全局变量的结果出现错误

原因:
    balance += n-----》第一步:x = balance + n   第二步:balance = x
    
    分析:
    t1:x1 = balance + n      x1 = 5
    
    t2:x2 = balance + n       x2 = 8
    t2:balance = x2        balance = 8
    
    t1:balance = x1        balance = 5
    t1:x1 = balance -n     x1 = 0
    t1:balance = x1        balance = 0
    
    t2:x2 = balance - n    x2 = -8
    t2:balance = x2        balance = -8
    
解决:
     只需要保证一个线程抢到时间片之后,能够将所有的代码执行完毕,其他线程处于等待状态
     
实现:
    给临界资源加锁,哪个线程抢到了时间片,则该线程持有锁 ,当临界资源的代码执行完毕,则再释放锁    
"""

3.线程锁

import  threading

# 创建一个锁对象
lock = threading.Lock()

# 假设银行卡余额
balance = 0

def change(n):
    global  balance
    balance += n
    balance -= n     # 0

def run(m):
    for _ in range(1000000):
        # 获取锁
        lock.acquire()
        try:
            change(m)
        finally:
            # 释放锁
            lock.release()

if __name__ == '__main__':
    t1 = threading.Thread(target=run,args=(5,))
    t2 = threading.Thread(target=run, args=(8,))
    t1.start()
    t2.start()

    t1.join()
    t2.join()

    print(f"balance={balance}")   # 理论上结果为0
import  threading

# 创建一个锁对象
lock = threading.Lock()

# 假设银行卡余额
balance = 0

def change(n):
    global  balance
    balance += n
    balance -= n     # 0

def run(m):
    for _ in range(1000000):
        # 进入with代码块,就会获取锁,当with代码块执行完毕,会自动释放锁
        with lock:
            change(m)


if __name__ == '__main__':
    t1 = threading.Thread(target=run,args=(5,))
    t2 = threading.Thread(target=run, args=(8,))
    t1.start()
    t2.start()

    t1.join()
    t2.join()

    print(f"balance={balance}")   # 理论上结果为0

4.定时线程

import  threading

def run():
    print("hello~~~~~")

if __name__ == '__main__':
    print("主线程启动")
    # Timer(seds,target)
    t = threading.Timer(5,run)
    t.start()

    t.join()

    print("主线程结束")

5.线程间的通信

import  threading,time

def func():
    # 创建一个事件的对象
    event = threading.Event()

    def run():
        for i in range(5):
            # wait():等待    clear():重置   notify():唤醒  notifyAll():唤醒所有
            # 等待/阻塞,等待事件的触发
            print("hello~~~~~~start")
            event.wait()
            print("hello~~~~~~end")

    threading.Thread(target=run).start()
    return event

e = func()

# 触发事件
for i in range(5):
    time.sleep(1)
    e.set()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值