Python进阶(进程-线程-Socket)

本文介绍了Linux系统中的并发与并行概念,详细讲解了进程的创建与管理,包括进程编号的获取,以及进程间不共享全局变量的特性。接着探讨了线程的使用,包括线程的创建、线程间共享全局变量的问题以及线程资源抢占导致的数据不一致。最后,提到了Socket通信的基础知识和TCP协议的面向连接与可靠性特性。
摘要由CSDN通过智能技术生成

一,操作系统

1.1,Linux系统基础操作

1.2,linux进程与线程

1.2.1并发,并行

(1)并发:在一段时间内交替的执行多个任务:对于单核CPU处理多任务,操作系统轮流让让各个任务交替执行,只不过每个任务处理的时间可能比较短,看起来是同时在处理。(当任务的数量大于CPU核心数就可看做事并发执行的。)
(2)并行:在一段时间内真正的同时执行多个任务。对于多核CPU处理多任务,操作系统会给CPU的每个内核安排一个执行的任务,多核真正实现多个任务同时处理

1.2.2进程

  1. 进程是资源分配的最小单位,他是操作系统进行资源分配和调度运行的基本单位,通俗讲,一个正在运行的程序就是一个进程,qq,微信等。在这里插入图片描述
  2. 进程的创建使用
    (1)导包
import multiprocessing

(2)创建进程类对象

proc1=multiprocessing.Process()

在这里插入图片描述
(3)启动进程

#(1)导包
import multiprocessing
import time
def coding():
    for i in range(3):
        print("正在写代码!")
        time.sleep(1)
def music():
    for i in range(3):
        print("正在听音乐!")
        time.sleep(1)
#(2)创建进程类对象,也叫子进程
proc1=multiprocessing.Process(target=coding)
proc2=multiprocessing.Process(target=music)
if __name__ == '__main__':
    # (3)启动进程
    proc1.start()
    proc2.start()

在这里插入图片描述

  1. 函数传参
    (1)在定义进程类对对象时,Process()的参数中可以使用args或者kwargs来确定函数需要输入的参数。args使用元组方式传参,kwargs使用字典方式。
#2函数有参数
import multiprocessing
import time
def coding(msg):
    for i in range(3):
        print(msg)
        time.sleep(1)
def music(msg):
    for i in range(3):
        print(msg)
        time.sleep(1)
proc1=multiprocessing.Process(target=coding,args=(2,))
#args=(2,)以元组形式进行传参,如果只有一个参数值需要添加逗号,
# 用以区分这不是一个括号,而是元组
proc2=multiprocessing.Process(target=music,kwargs={"msg":3})


if __name__ == '__main__':
    # (3)启动进程
    proc1.start()
    proc2.start()

在这里插入图片描述

1.2.3获取进程编号

  1. 当程序中的进程数量越来越多时,就无法区分主进程和子进程,为了方便管理每个进程都有自己的编号,通过获取进程编号就可以区分不同的进程。
  2. 两个获取进程编号的方法:getpid()#获取当前进程的编号getppid()#获取当前父进程的编号,需要导入os模块,使用os.getpid()获取进程编号。
import os
def GetPid():
    print("GetPid当前进程的编号:",os.getpid())
    print("父进程进程的编号:",os.getppid())
    if __name__ == '__main__':
    # (3)启动进程
    Proc3 = multiprocessing.Process(target=GetPid())
    Proc3.start()
  1. 一旦运行程序,系统会自动创建一个主进程用来管理代码中各行程序,
    在这里插入图片描述
    Proc3子进程有主进程创建,查看其父进程使用os.getppid()
    验证主进程创建子进程,查看下面两个子进程的父进程编号是否相同:
import multiprocessing
import time
import os
def coding(msg):
    print("coding父进程进程的编号:", os.getppid())
    # for i in range(3):
    #     print(msg)
    #     time.sleep(1)
def music(msg):
    print("music父进程进程的编号:", os.getppid())
    # for i in range(3):
    #     print(msg)
    #     time.sleep(1)
if __name__ == '__main__':
    # (3)启动进程
    proc1 = multiprocessing.Process(target=coding, args=(2,))
    # args=(2,)以元组形式进行传参,如果只有一个参数值需要添加逗号,
    # 用以区分这不是一个括号,而是元组
    proc2 = multiprocessing.Process(target=music, kwargs={"msg": 3})
    # Proc3 = multiprocessing.Process(target=GetPid())
    proc1.start()
    proc2.start()
    # Proc3.start()

在这里插入图片描述

1.2.4进程之间不共享全局变量

  1. 进程间是不共享全局变量的:实际上创建一个子进程就是把主进程的资源进行拷贝产生一个新的进程,这里主进程与子进程是相互独立的。而线程之间是共享全局变量的。
  2. 定义一个列表,然后定义两个进程一个用来写入数据,一个用来读取数据 ,看两个进程能否同时操作这一个列表中的数据。
#验证进程间不共享全局变量
import multiprocessing

mylist=list()
#写入数据
def write_data():
    for i in range(3):
        mylist.append(i)
        print(f"add:{i}")
    print(mylist)
#打印数据
def read_data():
    print(mylist)
if __name__ == '__main__':
    #创建写,读进程
    wirte_process=multiprocessing.Process(target=write_data)
    read_process=multiprocessing.Process(target=read_data)

    wirte_process.start()
    read_process.start()

在这里插入图片描述

1.2.5主进程与子进程结束顺序

  1. 主进程会等待所有子进程结束后才会结束:
    在这里插入图片描述
  2. 但是在代码中显示的结果不同:
import multiprocessing
import time
#创建工作函数
def work():
    for i in range(10):
        print("working!....")
        time.sleep(0.2)
if __name__ == '__main__':
    #创建子进程
    work_process=multiprocessing.Process(target=work)
    work_process.start()
    #延迟一秒使效果更明显
    time.sleep(1)
    print("主进程已结束")

在这里插入图片描述
由输出结果图可看到:子进程还没有结束主进程已经显示结束
3. 改进:设置守护主进程,主进程退出后子进程直接销毁,不再执行子进程的代码

import multiprocessing
import time
#创建工作函数
def work():
    for i in range(10):
        print("working!....")
        time.sleep(0.2)
if __name__ == '__main__':
    #创建子进程
    work_process=multiprocessing.Process(target=work)
    #设置守护主进程,主进程退出后子进程直接销毁,不再执行子进程的代码
    work_process.daemon=True
    work_process.start()
    #延迟一秒使效果更明显
    time.sleep(1)
    print("主进程已结束")

在这里插入图片描述
4. 第二种方法:直接手动销毁子进程 work_process.terminate()
在这里插入图片描述在这里插入图片描述

1.2.6线程

  1. python的多线程可以通过threading模块中的Tread类实现,每创建一个Thread对象就创建了一个线程。
import  threading
thread_obj1=threading.Thread()
"""
-group:暂无用处,未来功能的预留参数
-target:执行的目标任务名,,一般是方法名
-args:以元组的方式给执行任务传参
-kwargs:以字典方式给执行任务传参
-name:线程名,一般不用设置
"""
thread_obj.start()
  1. 单线程实例:
import time
def sing():
    while True:
        print("我在唱歌!")
        time.sleep(1)
def dance():
    while True:
        print("我在跳舞!")
        time.sleep()
if __name__ == '__main__':
    sing()
    dance()

在这里插入图片描述
由于是单线程,导致一次只能执行一个程序,跳舞的程序无法执行。
3. 多线程实现

import  threading
import time
def sing():
    while True:
        print("我在唱歌!")
        time.sleep(1)
def dance():
    while True:
        print("我在跳舞!")
        time.sleep(1)
#定义两个线程
thread_obj1 = threading.Thread(target=sing)
thread_obj2 = threading.Thread(target=dance)
if __name__ == '__main__':
    thread_obj1.start()
    thread_obj2.start()

在这里插入图片描述
4. 实例三,实现传参->args(元组),kwargs(字典 )

import  threading
import time
def sing(msg):
    while True:
        print(msg)
        time.sleep(1)

def dance(msg):
    while True:
        print(msg)
        time.sleep(1)
#定义两个线程

thread_obj1 = threading.Thread(target=sing,args=("唱歌中",))#以元组传参
thread_obj2 = threading.Thread(target=dance,kwargs={"msg":"跳舞中"})#以字典传参
if __name__ == '__main__':
    thread_obj1.start()
    thread_obj2.start()

注意:对于传入的元组参数,如果元组只有一个值,要加逗号,否则只是普通的数值,如threading.Thread(target=sing,args=("唱歌中",))
在这里插入图片描述

1.2.7主线程与子线程的执行顺序

(1)按理说主线程执行完毕后子线程也会停止执行,但实际并非如此:

import threading
import time
def work():
    for i in range(10):
        print(f"子线程执行第{i}次。")
        time.sleep(1)
if __name__ == '__main__':
    work_thre1=threading.Thread(target=work)
    work_thre1.start()#启动子进程

    time.sleep(1)
    print("主进程执行完毕!")

在这里插入图片描述
(2)通过设置守护 主进程:

 work_thre1.daemon=True

在这里插入图片描述
如下图:主进程结束时子进程虽然只执行一次,也不会继续执行。
在这里插入图片描述

1.2.8线程间共享全局变量

#04,线程间共享全局变量
import threading
import time
my_list=[]#全局变量
def write_data():
    for i in range(10):
        my_list.append(i)
    print(my_list)
def read_data():
    print(f"list内容:{my_list}")

if __name__ == '__main__':
    write_Thread=threading.Thread(target=write_data)
    read_Thread=threading.Thread(target=read_data)

    write_Thread.start()
    #设置时间间隔,确保充分时间执行 写入线程
    time.sleep(3)
    read_Thread.start()

在这里插入图片描述

1.2.9多线程资源抢占

(1)以下列task1(),task2()两个函数为例,分别将对全局变量num加一重复一千万次循环(数据大一些,太小的话执行太快,达不到验证的效果)。

import threading
import time
num = 0
def task1(nums):
    global num
    for i in range(nums):
        num += 1

    print("task1---num=%d" % num)

def task2(nums):
    global num
    for i in range(nums):
        num += 1
    print("task2---num=%d" % num)

if __name__ == '__main__':
    nums = 10000000
    t1 = threading.Thread(target=task1, args=(nums,))
    t2 = threading.Thread(target=task2, args=(nums,))

    t1.start()
    t2.start()
    # 因为主线程不会等子线程执行完就会执行,所以这里延迟五秒,确保最后执行。
    time.sleep(5)
    print("main----num=%d" % num)

在这里插入图片描述
如图,输出结果比较混乱,既没有一千万,最终结果也不是二千万。因为多线程运行时出现了资源竞争,即可以理解为,每个函数运行的时间都不确定,且互相影响,
如从初始值0开始,假设t1的线程先执行,执行到+1后,此时的num=1还未存储,然后即被叫停,t2开始执行,去获取num,获取到的num等于初始值0,然后其执行了+1并存储,存储后num=1,然后t2停止t1继续,再次存储num=1。即加了两次1,但是num还是只等于1。
因为t1和t2谁来运行的分配是完全随机的,所以最后加了两千万次1后值是小于2000万的。

1.2.10互斥锁

  • 某个线程要更改共享数据时,先将其锁定,此时资源的状态为"锁定",其他线程不能改变,只到该线程释放资源,将资源的状态变成"非锁定",其他的线程才能再次锁定该资源。
  • 互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。
#创建锁
mutex = threading.Lock()
#给线程上锁
mutex.acquire() # 上锁
xxxx锁定的内容xxxxx
mutex.release() # 解锁

整体代码:

import threading
import time
num = 0

def task1(nums):
    global num
    mutex.acquire()
    for i in range(nums):
        num += 1
    mutex.release()
    print("task1---num=%d" % num)


def task2(nums):
    global num
    mutex.acquire()
    for i in range(nums):
        num += 1
    mutex.release()
    print("task2---num=%d" % num)


if __name__ == '__main__':
    nums = 10000000
    mutex = threading.Lock()
    t1 = threading.Thread(target=task1, args=(nums,))
    t2 = threading.Thread(target=task2, args=(nums,))
    t1.start()
    t2.start()
    # 因为主线程不会等子线程执行完就会执行,所以这里延迟五秒,确保最后执行。
    time.sleep(5)
    print("main----num=%d" % num)

在这里插入图片描述
加锁过后,某线程占用公共资源(全局变量)时,其他进程处于等待状态。
在这里插入图片描述

1.2.11死锁

(1)死锁:是指多个线程同时被阻塞,其中一个或者全部线程都在等待某个资源,由于资源争夺而造成的一中僵局。若无外力推进,他们都将无法推进。由于无限期的阻塞,程序没有办法进行正常终止。例如两个线程都在等待对方的资源,而自己不肯放弃当前的资源,就会形成死锁状态。
(2)案例:支队take1,和task2进行加锁不进行解锁。task1对应的进行会持续霸占资源:

import threading
import time

num = 0
def task1(nums):
    global num
    print("task1.......")
    mutex.acquire()
    for i in range(nums):
        num += 1
    print("task1---num=%d" % num)


def task2(nums):
    global num
     print("task2.......")
    mutex.acquire()
    for i in range(nums):
        num += 1
    print("task2---num=%d" % num)


if __name__ == '__main__':
    nums = 10000000
    mutex = threading.Lock()
    t1 = threading.Thread(target=task1, args=(nums,))
    t2 = threading.Thread(target=task2, args=(nums,))

    t1.start()
    t2.start()
    # 因为主线程不会等子线程执行完就会执行,所以这里延迟五秒,确保最后执行。
    time.sleep(5)
    print("main----num=%d" % num)

在这里插入图片描述

1.3Socket

只拥有IP地址和端口号还无法实现网络之间的通信,还需要借助socket(套接字)来完成通讯,打电话只知道对方号码还不行,还需要一个可以接通的工具。

1.3.1Socket简介

  1. Socket是一种网络通信协议,它是为了解决不同计算机之间的通信而设计的。在计算机网络中,Socket是一种特殊的文件描述符,它允许应用程序通过网络进行通信。Socket提供了一种标准的接口,使得应用程序能够通过网络协议(如TCP或UDP)进行通信。
  2. Socket通常作为一种低级别的网络编程接口,它可以在不同的操作系统和编程语言中使用。它提供了一种面向流和面向消息的数据传输方式,并且可以在客户端和服务器端之间建立连接。使用Socket可以实现各种网络应用程序,如Web服务器、邮件服务器、FTP服务器等。
  3. 在Socket编程中,一个Socket通常由一个IP地址和一个端口号组成。IP地址用于标识网络中的主机,而端口号则用于标识特定的应用程序。当应用程序向网络发送数据时,它会将数据发送到特定的IP地址和端口号,而当应用程序接收数据时,它会监听特定的端口号,并等待来自网络的数据包。
    总之:程序之间的网络数据传输可以通过socket完成,socket就是程序间网络数据通信的工具

1.3.2TCP介绍

  1. 在确定想要传输数据的对象IP地址后,还需要选择网络传输协议,保证程序间按照指定的传输规则进行数据通信。
    在这里插入图片描述
  2. TCP是传输控制协议:他是一种面向连接的可靠的基于字节流的传输层通讯协议。TCP协议通过三次握手建立连接,保证数据传输的可靠性和顺序性,并通过拥塞控制机制来保证网络的健康稳定。它将数据分割成适当的大小,通过IP协议进行传输,并在接收端重新组装数据。
1.3.2.1 TCP面向连接特性

(1)面向连接:TCP是一种面向连接的协议,这意味着在数据传输之前,发送方和接收方必须先建立一个连接。(打电话之前确保对面有接电话的条件,且对方可以随时接听)这个连接是一个虚拟的通道,用于在发送方和接收方之间传输数据。在连接建立之后,发送方和接收方可以通过这个连接传输数据,直到连接被关闭。
(2)建立的连接:建立连接的过程通常是通过三次握手实现的。在三次握手过程中,发送方首先向接收方发送一个 SYN 报文,表示请求建立连接;接收方收到 SYN 报文后,回复一个 SYN+ACK 报文,表示接收到请求并同意建立连接;最后,发送方再回复一个 ACK 报文,表示连接建立成功。这个过程中,双方都要确认对方的身份和可用性,以确保连接的正确性和稳定性。
(3)关闭连接:当数据传输完成或者连接出现问题时,发送方和接收方都可以关闭连接。关闭连接的过程也需要经过三次握手,这样可以确保双方都能正确地关闭连接。

1.3.2.2 TCP可靠性特性

(1)序列号和确认号机制:TCP将数据分割成若干个数据段,并为每个数据段分配一个序列号。接收方在接收到数据段后,会向发送方回复一个确认号,表示已收到该数据段。如果发送方在规定时间内未收到确认,就会重传该数据段。
(2)滑动窗口机制:TCP使用滑动窗口机制来控制发送方和接收方之间的数据传输速度。发送方会将数据发送到接收方,但是接收方并不会立即回复确认,而是在接收到一定数量的数据后才回复确认。这样可以避免发送方发送过多的数据,导致网络拥塞。
(3)重传机制:如果发送方在规定时间内未收到确认,就会重传该数据段。接收方在接收到重传的数据段后,会丢弃之前接收到的重复数据,并向发送方回复确认。
(4)拥塞控制机制:TCP使用拥塞控制机制来避免网络拥塞。发送方会根据网络状况来调整发送速度,以避免发送过多的数据导致网络拥塞。如果网络拥塞,发送方会减少发送速度,以避免进一步加剧网络拥塞。

1.3.3TCP客户端

1.3.4TCP服务端

1.4HTTP协议

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

泰勒今天想展开

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值