关于可变/不可变类型及多线/进程的记录

i += 1 与 i = i + 1在可变数据类型的不同


def assignment_heighten():
    """增强赋值"""
    list_one = [2, 3, 4]
    list_two = list_one
    list_two += list_one
    print("l    ist_two", list_two)
    print("list_one", list_one)
    print("list_one_id", id(list_one), "list_two_id", id(list_two))


def assignment_general():
    """普通赋值"""
    list_one = [2, 3, 4]
    list_two = list_one
    list_two = list_one + list_two
    print("list_two", list_two)
    print("list_one", list_one)
    print("list_one_id", id(list_one), "list_two_id", id(list_two))

assignment_heighten()
print("---------------------------")
assignment_general()

运行结果:

list_two [2, 3, 4, 2, 3, 4]
list_one [2, 3, 4, 2, 3, 4]
list_one_id 1725183689352 list_two_id 1725183689352

list_two [2, 3, 4, 2, 3, 4]
list_one [2, 3, 4]
list_one_id 1725183689352 list_two_id 1725183689416

a、b 在进行运算后依旧指向了同一个内存对象。例二则相反,a、b 分别指向了不同的内存对象,也就是说在例二中隐式的新建了一个内存对象。

这是一个值得注意的坑,警惕我们在使用增量赋值运算符来操作可变对象(如列表)
时可能会产生不可预测的结果。要解释这个问题,首先需要了解「Python共享引用」
的概念:在Python中,允许若干个不同的变量引用指向同一个内存对象。同时在前文
中也提到,增强赋值语句比普通赋值语句的效率更高,这是因为在 Python 源码中, 
增强赋值比普通赋值多实现了“写回”的功能,也就是说增强赋值在条件符合的情况下
(例如:操作数是一个可变类型对象)会以追加的方式来进行处理,而普通赋值则会
以新建的方式进行处理。这一特点导致了增强赋值语句中的变量对象始终只有一个,
Python解析器解析该语句时不会额外创建出新的内存对象。所以例一中变量 a、b 
的引用在最后依旧指向了同一个内存对象;相反,对于普通赋值运算语句,Python解
析器无法分辨语句中的两个同名变量
(例如:b = b +1)是否应该为同一内存对象,所以干脆再创建出一个新的内存对象
用来存放最后的运算结果,所以例二中的a、b,从原来指向同一内存对象,到最后分
别指向了个不同的内存对象。
    这是一个不为人所熟知的问题,我们能得到的结论就是:尽量不要使用
增量赋值运算符来处理任何可变类型对象,除非你对上述问题有了足够
的了解。

多态性和鸭子类型

多态:父类的引用指向子类的对象

class Animal(object):
    def running(self):
        print("Animal is running")


class Dog(Animal):
    def running(self):
        print("Dog is running")


class Cat(Animal):
    def running(self):
        print("Cat is running")


class Sb:
    def running(self):
        print("Sb is running")


def use_method(obj):
    obj.running()


use_method(Dog())
use_method(Sb())

>
结果 : Dog is running
Sb is running

你会发现,新增一个Animal的子类,不必对use_method()做任何修改,实际上,任何依赖Animal作为参数的函数或者方法都可以不加修改地正常运行,原因就在于多态。

多态的作用:

对于一个变量,我们只需要知道它是Animal类型,无需确切地知道它的子类型,就可以放心地调用running()方法,而具体调用的running()方法是作用在Animal、Dog、Cat还是Dog对象上,由运行时该对象的确切类型决定,这就是多态真正的威力:调用方只管调用,不管细节,而当我们新增一种Animal的子类时,只要确保running()方法编写正确,不用管原来的代码是如何调用的。这就是著名的“开闭”原则:

对扩展开放:允许新增Animal子类;

对修改封闭:不需要修改依赖Animal类型的use_method等函数。


对于静态语言(例如Java)来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法调用running()方法。

对于Python这样的动态语言来说,则不一定需要传入Animal类型。我们只需要保证传入的对象有一个running()方法就可以了这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。
动态语言的鸭子类型特点决定了继承不像静态语言那样是必须的。

线程和进程实现多任务

定义

  • 进程是资源的分配单位
  • 线程只是CPU轮流调度单位
  • 概括:进程和线程都是一个时间段的描述,是CPU工作时间段的描述。

进程和线程的深入理解

一个必须知道的事实:执行一段程序代码,实现一个功能的过程介绍 ,当得到CPU的时候,相关的资源必须也已经就位,就是显卡啊,环境变量,有内存,打开的文件,映射的网络端口须就位,然后CPU开始执行。这里除了CPU以外所有的就构成了这个程序的执行环境,也就是我们所定义的程序上下文。当这个程序执行完了,或者分配给他的CPU执行时间用完了,那它就要被切换出去,等待下一次CPU的临幸。在被切换出去的最后一步工作就是保存程序上下文,因为这个是下次他被CPU临幸的运行环境,必须保存。串联起来的事实:前面讲过在CPU看来所有的任务都是一个一个的轮流执行的,具体的轮流方法就是:先加载程序A的上下文,然后开始执行A,保存程序A的上下文,调入下一个要执行的程序B的程序上下文,然后开始执行B,保存程序B的上下文
========= 重要的东西出现了========
进程和线程就是这样的背景出来的,两个名词不过是对应的CPU时间段的描述,名词就是这样的功能。进程就是包换上下文切换的程序执行时间总和 = CPU加载上下文+CPU执行+CPU保存上下文线程是什么呢?进程的颗粒度太大,每次都要有上下的调入,保存,调出。如果我们把进程比喻为一个运行在电脑上的软件,那么一个软件的执行不可能是一条逻辑执行的,必定有多个分支和多个程序段,就好比要实现程序A,实际分成 a,b,c等多个块组合而成。那么这里具体的执行就可能变成:程序A得到CPU =》CPU加载上下文,开始执行程序A的a小段,然后执行A的b小段,然后再执行A的c小段,最后CPU保存A的上下文。这里a,b,c的执行是共享了A的上下文,CPU在执行的时候没有进行上下文切换的。这里的a,b,c就是线程,也就是说线程是共享了进程的上下文环境,的更为细小的CPU时间段。

多任务设计

要实现多任务,通常我们会设计Master-Worker模式,Master负责分配任务,Worker负责执行任务,因此,多任务环境下,通常是一个Master,多个Worker。
如果用多进程实现Master-Worker,主进程就是Master,其他进程就是Worker。
如果用多线程实现Master-Worker,主线程就是Master,其他线程就是Worker。

多进程的优点

多进程模式最大的优点就是稳定性高,因为一个子进程崩溃了,不会影响主进程和其他子进程。(当然主进程挂了所有进程就全挂了,但是Master进程只负责分配任务,挂掉的概率低)著名的Apache最早就是采用多进程模式。

多进程的缺点

多进程模式的缺点是创建进程的代价大,在Unix/Linux系统下,用fork调用还行,在Windows下创建进程开销巨大。另外,操作系统能同时运行的进程数也是有限的,在内存和CPU的限制下,如果有几千个进程同时运行,操作系统连调度都会成问题。

多线程缺点

任何一个线程挂掉都可能直接造成整个进程崩溃,因为所有线程共享进程的内存。在Windows上,如果一个线程执行的代码出了问题,你经常可以看到这样的提示:“该程序执行了非法操作,即将关闭”,其实往往是某个线程出了问题,但是操作系统会强制结束整个进程

管于多进程全局共享的一个小延伸

-- coding:utf-8 --

author = ‘bobby’
date = ‘2018/4/6 9:56’
title = ‘prodcons_queue’

from random import randint
from time import ctime, sleep
from multiprocessing import Queue,Pool,Process

# queue1 = Queue(32)
#多进程进程间不共享全局变量限于不可变类型,可变类型全局共享
#根本原因在于共享变量的是否可以追加性
a=[]

# 创建写/读入队列的方法

# -*- coding:utf-8 -*-
__author__ = 'bobby'
__date__ = '2018/4/6 9:56'
__title__ = 'prodcons_queue'

from  random import randint
from time import ctime, sleep
from multiprocessing import Queue,Pool,Process

# queue1 = Queue(32)

#多进程进程间不共享全局变量限于不可变类型,可变类型全局共享
a=[] #创建了空队列

# 创建写/读入队列的方法
def wirte_queue():
    # queue1.put('xxx', 1)
    a.append(1)
    print('写入到队列,当前队列长度为:', len(a))
def read_queue():
    print('开始写出队列')
    a.pop(a[0])
    print(a)
    print('写出后当前队列长度为:', len(a))


def writer(loops):
    for i in range(loops):
        wirte_queue()
        # sleep(randint(1, 3))


def reader(loops):
    for i in range(loops):
        read_queue()
        # sleep(randint(2, 5))

funcs = [writer, reader]  #线程函数列表
nfuncs = len(funcs)
nloops = [randint(5, 8),randint(3, 5)]
 # 实例化队列,创建一个FIFO的队列,最大容量为32个

def main():
    po = Pool(2)
    # for i in range(nfuncs):
    #     p=Process(target=funcs[i],args=(q,nloops[i]))
    #     p.start()
    for i in range(nfuncs):
        po.apply_async(funcs[i], (nloops[i],))

    print('---------------------开始---------------------')
    po.close()
    po.join()
    print('---------------------结束---------------------')

if __name__ == '__main__':
    main()

结果:

---------------------开始---------------------
写入到队列,当前队列长度为: 1
写入到队列,当前队列长度为: 2
写入到队列,当前队列长度为: 3
写入到队列,当前队列长度为: 4
写入到队列,当前队列长度为: 5
写入到队列,当前队列长度为: 6
写入到队列,当前队列长度为: 7
写入到队列,当前队列长度为: 8
开始写出队列
[1, 1, 1, 1, 1, 1, 1]
写出后当前队列长度为: 7
开始写出队列
[1, 1, 1, 1, 1, 1]
写出后当前队列长度为: 6
开始写出队列
[1, 1, 1, 1, 1]
写出后当前队列长度为: 5
---------------------结束---------------------

猜你喜欢

转载自blog.csdn.net/newbietan/article/details/79851818