Python之路【第十六篇】:Python并发编程

一、进程和线程

进程

假如有两个程序A和B,程序A在执行到一半的过程中,需要读取大量的数据输入(I/O操作),
    而此时CPU只能静静地等待任务A读取完数据才能继续执行,这样就白白浪费了CPU资源。
    是不是在程序A读取数据的过程中,让程序B去执行,当程序A读取完数据之后,让
    程序B暂停,然后让程序A继续执行?
    当然没问题,但这里有一个关键词:切换
    既然是切换,那么这就涉及到了状态的保存,状态的恢复,加上程序A与程序B所需要的系统资
    源(内存,硬盘,键盘等等)是不一样的。自然而然的就需要有一个东西去记录程序A和程序B
    分别需要什么资源,怎样去识别程序A和程序B等等,所以就有了一个叫进程的抽象

    进程定义:

    进程就是一个程序在一个数据集上的一次动态执行过程。
    进程一般由程序、数据集、进程控制块三部分组成。
    我们编写的程序用来描述进程要完成哪些功能以及如何完成;
    数据集则是程序在执行过程中所需要使用的资源;
    进程控制块用来记录进程的外部特征,描述进程的执行变化过程,系统可以利用它来控制和管理进程,它是系
    统感知进程存在的唯一标志。

    举一例说明进程:
             想象一位有一手好厨艺的计算机科学家正在为他的女儿烘制生日蛋糕。他有做生日蛋糕的食谱,厨房里有所需
             的原料:面粉、鸡蛋、糖、香草汁等。在这个比喻中,做蛋糕的食谱就是程序(即用适当形式描述的算法)计算机科学家就是处理器(cpu),
             而做蛋糕的各种原料就是输入数据。进程就是厨师阅读食谱、取来各种原料以及烘制蛋糕等一系列动作的总和。
             现在假设计算机科学家的儿子哭着跑了进来,说他的头被一只蜜蜂蛰了。计算机科学家就记录下他
             照着食谱做到哪儿了(保存进程的当前状态),然后拿出一本急救手册,按照其中的指示处理蛰伤。这
             里,我们看到处理机从一个进程(做蛋糕)切换到另一个高优先级的进程(实施医疗救治),每个进程
             拥有各自的程序(食谱和急救手册)。当蜜蜂蛰伤处理完之后,这位计算机科学家又回来做蛋糕,从他
             离开时的那一步继续做下去。

线程

线程的出现是为了降低上下文切换的消耗,提高系统的并发性,并突破一个进程只能干一样事的缺陷,
使到进程内并发成为可能。

假设,一个文本程序,需要接受键盘输入,将内容显示在屏幕上,还需要保存信息到硬盘中。若只有
一个进程,势必造成同一时间只能干一样事的尴尬(当保存时,就不能通过键盘输入内容)。若有多
个进程,每个进程负责一个任务,进程A负责接收键盘输入的任务,进程B负责将内容显示在屏幕上的
任务,进程C负责保存内容到硬盘中的任务。这里进程A,B,C间的协作涉及到了进程通信问题,而且
有共同都需要拥有的东西-------文本内容,不停的切换造成性能上的损失。若有一种机制,可以使
任务A,B,C共享资源,这样上下文切换所需要保存和恢复的内容就少了,同时又可以减少通信所带
来的性能损耗,那就好了。是的,这种机制就是线程。

线程也叫轻量级进程,它是一个基本的CPU执行单元,也是程序执行过程中的最小单元,由线程ID、程序
计数器、寄存器集合和堆栈共同组成。线程的引入减小了程序并发执行时的开销,提高了操作系统的并发
性能。线程没有自己的系统资源。

线程进程的关系区别:

1、一个程序至少有一个进程,一个进程至少有一个线程.(进程可以理解成线程的容器)

2、进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。

3、线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和
程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

4、进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调
度的一个独立单位. 
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程
自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈)但是
它可与同属一个进程的其他的线程共享进程所拥有的全部资源. 
一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.

python的GIL

In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)

上面的核心意思就是,无论你启多少个线程,你有多少个cpu, Python在执行的时候会淡定的在同一时刻只允许一个线程运行

二、python的线程与threading模块

1、线程的两种调用方式

threading 模块建立在thread模块之上。thread模块以低级、原始的方式来处理和控制线程,而threading 模块通过对thread进行二次封装,提供了更方便的api来处理线程。

直接调用:

import threading
import time

def sayhi(num): #定义每个线程要运行的函数

    print("running on number:%s" %num)

    time.sleep(3)

if __name__ == ‘__main__‘:

    t1 = threading.Thread(target=sayhi,args=(1,)) #生成一个线程实例
    t2 = threading.Thread(target=sayhi,args=(2,)) #生成另一个线程实例

    t1.start() #启动线程
    t2.start() #启动另一个线程

    print(t1.getName()) #获取线程名
    print(t2.getName())

继承式调用:

import threading
import time

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

    def run(self):#定义每个线程要运行的函数

        print("running on number:%s" %self.num)

        time.sleep(3)

if __name__ == ‘__main__‘:

    t1 = MyThread(1)
    t2 = MyThread(2)
    t1.start()
    t2.start()

    print("ending......")                                                                       

三、threading.thread的实例方法

join&Daemon方法

import threading
from time import ctime,sleep
import time

def ListenMusic(name):

        print ("Begin listening to %s. %s" %(name,ctime()))
        sleep(3)
        print("end listening %s"%ctime())

def RecordBlog(title):

        print ("Begin recording the %s! %s" %(title,ctime()))
        sleep(5)
        print(‘end recording %s‘%ctime())

threads = []

t1 = threading.Thread(target=ListenMusic,args=(‘杀手‘,))
t2 = threading.Thread(target=RecordBlog,args=(‘python线程‘,))

threads.append(t1)
threads.append(t2)

if __name__ == ‘__main__‘:

    for t in threads:
        #t.setDaemon(True) #注意:一定在start之前设置
        t.start()
        # t.join()
    # t1.join()
    t1.setDaemon(True)

    #t2.join()########考虑这三种join位置下的结果?
    print ("all over %s" %ctime())
join():在子线程完成运行之前,这个子线程的父线程将一直被阻塞。

setDaemon(True):

         将线程声明为守护线程,必须在start() 方法调用之前设置, 如果不设置为守护线程程序会被无限挂起。这个方法基本和join是相反的。

         当我们 在程序运行中,执行一个主线程,如果主线程又创建一个子线程,主线程和子线程 就分兵两路,分别运行,那么当主线程完成

         想退出时,会检验子线程是否完成。如 果子线程未完成,则主线程会等待子线程完成后再退出。但是有时候我们需要的是 只要主线程

         完成了,不管子线程是否完成,都要和主线程一起退出,这时就可以 用setDaemon方法啦

其他方法:

# run():  线程被cpu调度后自动执行线程对象的run方法
# start():启动线程活动。
# isAlive(): 返回线程是否活动的。
# getName(): 返回线程名。
# setName(): 设置线程名。

threading模块提供的一些方法:
# threading.currentThread(): 返回当前的线程变量。
# threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
# threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

四、同步锁(Lock)

import threading
import time

def addNum():
    global num #在每个线程中都获取这个全局变量
    #num-=1

    temp=num
    #print(‘--get num:‘,num )
    time.sleep(0.1)
    num =temp-1 #对此公共变量进行-1操作

num = 1000  #设定一个共享变量
thread_list = []
for i in range(1000):
    t = threading.Thread(target=addNum)
    t.start()
    thread_list.append(t)

for t in thread_list: #等待所有线程执行完毕
    t.join()

print(‘final num:‘, num )

观察:time.sleep(0.1)  /0.001/0.0000001 结果分别是多少?

多个线程都在同时操作同一个共享资源,所以造成了资源破坏,怎么办呢?(join会造成串行,失去所线程的意义)

我们可以通过同步锁来解决这种问题

R=threading.Lock()

####
def sub():
    global num
    R.acquire()
    temp=num-1
    time.sleep(0.1)
    num=temp
    R.release()

五、递归锁

在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁,因为系统判断这部分资源都正在使用,所以这两个线程在无外力作用下将一直等待下去。死锁的例子

import threading,time

class myThread(threading.Thread):
    def doA(self):
        lockA.acquire()
        print(self.name,"gotlockA",time.ctime())
        time.sleep(3)
        lockB.acquire()
        print(self.name,"gotlockB",time.ctime())
        lockB.release()
        lockA.release()

    def doB(self):
        lockB.acquire()
        print(self.name,"gotlockB",time.ctime())
        time.sleep(2)
        lockA.acquire()
        print(self.name,"gotlockA",time.ctime())
        lockA.release()
        lockB.release()

    def run(self):
        self.doA()
        self.doB()
if __name__=="__main__":

    lockA=threading.Lock()
    lockB=threading.Lock()
    threads=[]
    for i in range(5):
        threads.append(myThread())
    for t in threads:
        t.start()
    for t in threads:
        t.join()#等待线程结束,后面再讲。

解决办法:使用递归锁,将

lockA=threading.Lock()
lockB=threading.Lock()<br>#--------------<br>lock=threading.RLock()

为了支持在同一线程中多次请求同一资源,python提供了“可重入锁”:threading.RLock。RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次acquire。直到一个线程所有的acquire都被release,其他的线程才能获得资源。

应用

import time

import threading

class Account:
    def __init__(self, _id, balance):
        self.id = _id
        self.balance = balance
        self.lock = threading.RLock()

    def withdraw(self, amount):

        with self.lock:
            self.balance -= amount

    def deposit(self, amount):
        with self.lock:
            self.balance += amount

    def drawcash(self, amount):#lock.acquire中嵌套lock.acquire的场景

        with self.lock:
            interest=0.05
            count=amount+amount*interest

            self.withdraw(count)

def transfer(_from, to, amount):

    #锁不可以加在这里 因为其他的其它线程执行的其它方法在不加锁的情况下数据同样是不安全的
     _from.withdraw(amount)

     to.deposit(amount)

alex = Account(‘alex‘,1000)
yuan = Account(‘yuan‘,1000)

t1=threading.Thread(target = transfer, args = (alex,yuan, 100))
t1.start()

t2=threading.Thread(target = transfer, args = (yuan,alex, 200))
t2.start()

t1.join()
t2.join()

print(‘>>>‘,alex.balance)
print(‘>>>‘,yuan.balance)

六、同步条件(Event)

import threading,time
class Boss(threading.Thread):
    def run(self):
        print("BOSS:今晚大家都要加班到23:00。")
        print(event.isSet())
        event.set()
        time.sleep(5)
        print("BOSS:<23:00>可以下班了。")
        print(event.isSet())
        event.set()
class Worker(threading.Thread):
    def run(self):
        event.wait()
        print("Worker:哎……苦逼!")
        time.sleep(1)
        event.clear()
        event.wait()
        print("Worker:OhYeah!")
if __name__=="__main__":
    event=threading.Event()
    threads=[]
    for i in range(5):
        threads.append(Worker())
    threads.append(Boss())
    for t in threads:
        t.start()
    for t in threads:
        t.join()

七、信号量(Semaphore)

信号量用来控制线程并发数的,BoundedSemaphore或Semaphore管理一个内置的计数 器,每当调用acquire()时-1,调用release()时+1。

计数器不能小于0,当计数器为 0时,acquire()将阻塞线程至同步锁定状态,直到其他线程调用release()。(类似于停车位的概念)

BoundedSemaphore与Semaphore的唯一区别在于前者将在调用release()时检查计数 器的值是否超过了计数器的初始值,如果超过了将抛出一个异常。

import threading,time
class myThread(threading.Thread):
    def run(self):
        if semaphore.acquire():
            print(self.name)
            time.sleep(3)
            semaphore.release()
if __name__=="__main__":
    semaphore=threading.Semaphore(5)
    thrs=[]
    for i in range(100):
        thrs.append(myThread())
    for t in thrs:
        t.start()                                        

八、多线程利器-----队列(queue)

 

1、列表是不安全的数据结构

import threading,time

li=[1,2,3,4,5]

def pri():
    while li:
        a=li[-1]
        print(a)
        time.sleep(1)
        try:
            li.remove(a)
        except Exception as e:
            print(‘----‘,a,e)

t1=threading.Thread(target=pri,args=())
t1.start()
t2=threading.Thread(target=pri,args=())
t2.start()

思考:如何通过对列来完成上述功能?

queue is especially useful in threaded programming when information must be exchanged safely between multiple threads.

2、queue队列类的方法

创建一个“队列”对象
import Queue
q = Queue.Queue(maxsize = 10)
Queue.Queue类即是一个队列的同步实现。队列长度可为无限或者有限。可通过Queue的构造函数的可选参数maxsize来设定队列长度。如果maxsize小于1就表示队列长度无限。

将一个值放入队列中
q.put(10)
调用队列对象的put()方法在队尾插入一个项目。put()有两个参数,第一个item为必需的,为插入项目的值;第二个block为可选参数,默认为
1。如果队列当前为空且block为1,put()方法就使调用线程暂停,直到空出一个数据单元。如果block为0,put方法将引发Full异常。

将一个值从队列中取出
q.get()
调用队列对象的get()方法从队头删除并返回一个项目。可选参数为block,默认为True。如果队列为空且block为True,
get()就使调用线程暂停,直至有项目可用。如果队列为空且block为False,队列将引发Empty异常。

Python Queue模块有三种队列及构造函数:
1、Python Queue模块的FIFO队列先进先出。   class queue.Queue(maxsize)
2、LIFO类似于堆,即先进后出。               class queue.LifoQueue(maxsize)
3、还有一种是优先级队列级别越低越先出来。        class queue.PriorityQueue(maxsize)

此包中的常用方法(q = Queue.Queue()):
q.qsize() 返回队列的大小
q.empty() 如果队列为空,返回True,反之False
q.full() 如果队列满了,返回True,反之False
q.full 与 maxsize 大小对应
q.get([block[, timeout]]) 获取队列,timeout等待时间
q.get_nowait() 相当q.get(False)
非阻塞 q.put(item) 写入队列,timeout等待时间
q.put_nowait(item) 相当q.put(item, False)
q.task_done() 在完成一项工作之后,q.task_done() 函数向任务已经完成的队列发送一个信号
q.join() 实际上意味着等到队列为空,再执行别的操作

3、other mode:

import queue

#先进后出

q=queue.LifoQueue()

q.put(34)
q.put(56)
q.put(12)

#优先级
# q=queue.PriorityQueue()
# q.put([5,100])
# q.put([7,200])
# q.put([3,"zhurui"])
# q.put([4,{"name":"simon"}])

while 1:

  data=q.get()
  print(data)

生产者消费者模型:

为什么要使用生产者和消费者模式

在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。

什么是生产者消费者模式

生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。

这就像,在餐厅,厨师做好菜,不需要直接和客户交流,而是交给前台,而客户去饭菜也不需要不找厨师,直接去前台领取即可,这也是一个结耦的过程。

import time,random
import queue,threading

q = queue.Queue()

def Producer(name):
  count = 0
  while count <10:
    print("making........")
    time.sleep(random.randrange(3))
    q.put(count)
    print(‘Producer %s has produced %s baozi..‘ %(name, count))
    count +=1
    #q.task_done()
    #q.join()
    print("ok......")
def Consumer(name):
  count = 0
  while count <10:
    time.sleep(random.randrange(4))
    if not q.empty():
        data = q.get()
        #q.task_done()
        #q.join()
        print(data)
        print(‘\033[32;1mConsumer %s has eat %s baozi...\033[0m‘ %(name, data))
    else:
        print("-----no baozi anymore----")
    count +=1

p1 = threading.Thread(target=Producer, args=(‘A‘,))
c1 = threading.Thread(target=Consumer, args=(‘B‘,))
# c2 = threading.Thread(target=Consumer, args=(‘C‘,))
# c3 = threading.Thread(target=Consumer, args=(‘D‘,))
p1.start()
c1.start()
# c2.start()
# c3.start()

九、多进程模块 multiprocessing

由于GIL的存在,python中的多线程其实并不是真正的多线程,如果想要充分地使用多核CPU的资源,在python中大部分情况需要使用多进程。

multiprocessing包是Python中的多进程管理包。与threading.Thread类似,它可以利用multiprocessing.Process对象来创建一个进程。该进程可以运行在Python程序内部编写的函数。该Process对象与Thread对象的用法相同,也有start(), run(), join()的方法。此外multiprocessing包中也有Lock/Event/Semaphore/Condition类 (这些对象可以像多线程那样,通过参数传递给各个进程),用以同步进程,其用法与threading包中的同名类一致。所以,multiprocessing的很大一部份与threading使用同一套API,只不过换到了多进程的情境

1、进程的调用

调用方式1

from multiprocessing import Process
import time
def f(name):
    time.sleep(1)
    print(‘hello‘, name,time.ctime())

if __name__ == ‘__main__‘:
    p_list=[]
    for i in range(3):
        p = Process(target=f, args=(‘alvin‘,))
        p_list.append(p)
        p.start()
    for i in p_list:
        p.join()
    print(‘end‘)

调用方式2

from multiprocessing import Process
import time

class MyProcess(Process):
    def __init__(self):
        super(MyProcess, self).__init__()
        #self.name = name

    def run(self):
        time.sleep(1)
        print (‘hello‘, self.name,time.ctime())

if __name__ == ‘__main__‘:
    p_list=[]
    for i in range(3):
        p = MyProcess()
        p.start()
        p_list.append(p)

    for p in p_list:
        p.join()

    print(‘end‘)

例子3:

from multiprocessing import Process
import os
import time
def info(title):

    print("title:",title)
    print(‘parent process:‘, os.getppid())
    print(‘process id:‘, os.getpid())

def f(name):
    info(‘function f‘)
    print(‘hello‘, name)

if __name__ == ‘__main__‘:
    info(‘main process line‘)
    time.sleep(1)
    print("------------------")
    p = Process(target=info, args=(‘yuan‘,))
    p.start()
    p.join()

原文地址:https://www.cnblogs.com/hackerer/p/11440379.html

时间: 2024-11-06 09:51:10

Python之路【第十六篇】:Python并发编程的相关文章

Python之路(第二十六篇) 面向对象进阶:内置方法

一.__getattribute__ object.__getattribute__(self, name) 无条件被调用,通过实例访问属性.如果class中定义了__getattr__(),则__getattr__()不会被调用(除非显示调用或引发AttributeError异常) class Foo: ? def __init__(self,x): self.x = x ? def __getattr__(self, item): print("执行__getattr__") ?

Python之路【第六篇】:socket

Python之路[第六篇]:socket Socket socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,应用程序通常通过"套接字"向网络发出请求或者应答网络请求. socket起源于Unix,而Unix/Linux基本哲学之一就是"一切皆文件",对于文件用[打开][读写][关闭]模式来操作.socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO.打开.关闭

Python开发【第十六篇】:AJAX全套

Python开发[第十六篇]:AJAX全套 概述 对于WEB应用程序:用户浏览器发送请求,服务器接收并处理请求,然后返回结果,往往返回就是字符串(HTML),浏览器将字符串(HTML)渲染并显示浏览器上. 1.传统的Web应用 一个简单操作需要重新加载全局数据 2.AJAX AJAX,Asynchronous JavaScript and XML (异步的JavaScript和XML),一种创建交互式网页应用的网页开发技术方案. 异步的JavaScript:使用 [JavaScript语言] 以

Python自动化 【第十六篇】:JavaScript作用域和Dom收尾

本节内容: javascript作用域 DOM收尾 JavaScript作用域 JavaScript的作用域一直以来是前端开发中比较难以理解的知识点,对于JavaScript的作用域主要记住几句话,走遍天下都不怕... 一.JavaScript中无块级作用域 在Java或C#中存在块级作用域,即:大括号也是一个作用域. 1 public static void main () 2 { 3 if(1==1){ 4 String name = "seven"; 5 } 6 System.o

Python之路【第六篇】python基础 之面向对象(一)

一.三大编程范式 1.面向过程编程 2.函数式编程 3.面向对象编程 二.编程进化论 1.编程最开始就是无组织无结构,从简单控制流中按步写指令 2.从上述的指令中提取重复的代码块或逻辑,组织到一起(比方说,你定义了一个函数),便实现了代码重用,且代码由无结构走向了结构化,创建程序的过程变得更具逻辑性 3.我们定义函数都是独立于函数外定义变量,然后作为参数传递给函数,这意味着:数据与动作是分离的 4.如果我们把数据和动作内嵌到一个结构(函数或类)里面,那么我们就有了一个'对象系统'(对象就是数据与

Python之路【第六篇】:Python基础(22)——生成器和迭代器

迭代器 迭 代器是访问集合元素的一种方式.迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束.迭代器只能往前不会后退,不过这也没什么,因为人们 很少在迭代途中往后退.另外,迭代器的一大优点是不要求事先准备好整个迭代过程中所有的元素.迭代器仅仅在迭代到某个元素时才计算该元素,而在这之前或之 后,元素可以不存在或者被销毁.这个特点使得它特别适合用于遍历一些巨大的或是无限的集合,比如几个G的文件. 特点: 访问者不需要关心迭代器内部的结构,仅需通过__next()__(Python2.x

Python之路【第六篇】:Python基础(21)——正则表达式

正则表达式 字符串是编程时涉及到的最多的一种数据结构,对字符串进行操作的需求几乎无处不在.比如判断一个字符串是否是合法的Email地址,虽然可以编程提取@前后的子串,再分别判断是否是单词和域名,但这样做不但麻烦,而且代码难以复用. 正则表达式是一种用来匹配字符串的强有力的武器.它的设计思想是用一种描述性的语言来给字符串定义一个规则,凡是符合规则的字符串,我们就认为它“匹配”了,否则,该字符串就是不合法的. 下面这张图展示了使用正则表达式匹配的流程 python的re模块 Python通过re模块

Python之路(第十四篇)os模块

一.os模块 1.os.getcwd() 获取当前工作目录(当前工作目录默认都是当前文件所在的文件夹) import os print(os.getcwd()) 2.os.chdir(path) 改变当前工作目录,os.chdir()参数为路径 import os print(os.getcwd()) print(os.chdir(r"d:\python\exercise\test_package\a")) print(os.getcwd()) 输出结果 D:\06python\exe

Python之路(第十八篇)shutil 模块、zipfile模块、configparser模块

一.shutil 模块 1.shutil.copyfileobj(fsrc, fdst[, length]) 将文件内容拷贝到另一个文件中,需要打开文件 import shutil shutil.copyfileobj(open("old_test.txt","r"),open("new_test.txt","w")) 输出结果 2.shutil.copyfile(src,dst) 复制文件内容到另外一个文件,不需要打开文件,

Python之路(十五):网络编程(上)

socket编程 本篇介绍socket是基于什么来的,为什么要知道互联网底层实现通信的原理 一.客户端/服务端架构 即C/S架构,包括 1.硬件C/S架构(打印机) 2.软件C/S架构(web服务) 美好的愿望: 最常用的软件服务器是 Web 服务器.一台机器里放一些网页或 Web 应用程序,然后启动 服务.这样的服务器的任务就是接受客户的请求,把网页发给客户(如用户计算机上的浏览器),然 后等待下一个客户请求.这些服务启动后的目标就是"永远运行下去".虽然它们不可能实现这样的 目标,