Python后端技术栈(四)--操作系统

正文共:6014 字 8 图 预计阅读时间:16 分钟

每日分享

Do more with less.

事半功倍。少花钱多办事。

这句话有两种含义,大家可以从多方面去理解这句英文。

小闫语录

好钢用在刀刃上。请朝着正确的方向用正确的方式努力,否则不要埋怨自己的勤奋得不到回报。

1.4操作系统

上篇文章传送门『我是个链接

上篇文章对 Python 的一些设计模式做了归纳概括,这些模式需要大家动手利用 demo 实现理解一下。

本篇文章将开始操作系统的相关内容,开始咯~

1.4.1 Linux 常用命令

对于为什么学习 Linux 不需要我多说了吧?算了,为了内容的完整性,我还是简单的说一下。

在工作中大部分的应用都是跑在 Linux Server 上面,我们常常需要远程连接进行操作,所以熟练在 Linux 服务器上的操作是很有必要的。我们需要了解 Linux 工作原理和常用的一些工具,比如查看文件、进程、内存相关的一些命令用来调试和排查错误。

也许你会很苦恼,记不住所有的命令。那是当然,命令其实不需要记忆,因为经常用到的一些命令你自然而然会记住,不常用的你记住也会忘记。忘记了怎么办呢?查手册咯~

Linux 系统命令那么多,如何知道一个命令的用法呢?

第一个方法,就是使用 man 这个命令查询用法,它就好比说明书一样,但是这玩意有点难看,文艺点的说法就做晦涩:

比如我们查询 tar 这个命令:

此命令用来压缩和解压文件的。

man tar

第二个方法就是使用工具自带的 help 命令。

比如查询 pip 这个命令的用法:

pip --help

说实话上面的两个方法虽然方便,但是总感觉不那么友好。听到这里,也许你猜到了我还有更好的方法。这里介绍一个 man 的替代工具 tldr 。这个工具需要先安装,我们可以使用如下命令:

pip install tldr

然后使用的时候如同 man 一样,同样查看 tar 命令的用法。

tldr tar

这个工具的好处就是它列举了一些示例,非常直观可读的方式让你快速掌握此命令。

1.4.1.1常用文件操作工具

1.chown 修改所有者,chmod 改变权限,chgrp 修改组。

2.ls/rm/cd/cp/mv/touch/rename/ln(软链接和硬链接)等。

其他命令都很简单,此处简单说一下链接。软链接就好比 Windows 下的一个快捷方式,它里面保存的是源文件的绝对路径;而硬链接则可以简单的理解为将文件复制了一份。硬链接和原来的文件没有什么区别,它们共享一个 inode 值(文件在文件系统上的唯一标识,操作系统其实是通过 inode 值访问硬盘上的区块的,只要有文件 inode 值指向硬盘上的区块,这个文件就始终不会消失);而软链接不共享 inode 值。

3.locate(定位一个文件)、find(查找文件)、grep(查找一些特定的字符串)

1.4.1.2文件或者日志查看工具

1.编辑器 vi/vim/nano

2.cat/head/tail 查看文件

3.more/less 交互式查看文件

1.4.1.3常见的进程操作工具

1.ps 查看进程

2.kill 杀死进程

kill 这个命令虽然很常用,但是其原理很少有人明白。我们使用 kill pidkill-15pid 其实是一样的,系统会发送 SIGTERM 信号给对应的程序,当程序接收到该 signal 后,会发生下面的情况:1.程序立即停止;2.当程序释放相应的资源后再停止;3.程序可能会继续执行(比如程序正在等待 IO)。也就是说 SIGTERM 是会被阻塞的或者忽略的。那么我们就可以使用 kill-9pid这个命令了,系统会发送 SIGKILL 这个必杀指令给对应的程序。

3.top/htop 监控进程

top 很好用,但是当你使用过 htop 后,你会立马放弃之前的想法。下面列举几个 htop 的优点:可以横向或者纵向滚动浏览进程列表;启动快;杀进程不需要输入进程号;支持鼠标操作。

1.4.1.4内存操作命令

free 查看可用内存,排查内存泄漏的问题。下面我们就 free 命令的一些参数进行解释:

python@ubuntu:~$ free
              total        used        free      shared  buff/cache   available
Mem:        4028884     3022156      200808       61456      805920      639432
Swap:             0           0           0

total 表示总计物理内存和交换空间的大小。used 表示已经使用物理内存和交换空间多少。free 表示可用物理内存和交换空间大小。shared 表示多个进程共享的内存总额。buff/cache 表示被 buffer 和 cache 使用的物理内存大小。available 表示还可以被应用程序使用的物理内存大小。swap 表示交换空间的使用情况。 Mem 表示内存的使用情况。

1.4.1.5常见的网络操作命令

1.ifconfig 查看网卡信息

2.lsof/netstat 查看端口信息

3.ssh/scp 远程登录/复制。 tcpdump 抓包。

Linux 如何查看程序端口号?

netstat -tnulp

1.4.1.6常见用户/组操作命令

1.增加和修改用户:useradd/usermod

2.增加和修改组:groupadd/groupmod

下面我推荐一本学习 Linux 命令的书《鸟哥的 Linux 私房菜》,它还可以学习简单的 shell 脚本知识。

1.4.2进程和线程的区别

1.进程是对运行时程序的封装,是系统资源调度和分配的基本单位

2.线程是进程的子任务,cpu 调度和分配的基本单位,实现进程内并发

并行:并行是真正的同时执行,可以充分的利用多核 CPU 并发:并发其实是基于时间片的轮转。这么说也许很抽象,时间片就是非常短暂的一段时间,无限短,然后是先干一下这个,再干一下那个,所白了就是轮换,不是同时执行。

3.一个进程可以包含多个线程,线程依赖进程存在,并共享进程内存

1.4.2.1什么是线程安全

线程是基于进程存在的,而且共享资源,这样就会出现一个问题。如果多个线程同时去修改数据,某个线程的修改被另一个线程的修改覆盖掉,修改的数据丢失的情况就是线程安全问题。如何想要安全访问然后修改数据的话,我们就需要一些机制了。最常见的便是锁的机制,类似于上公共厕所。我进去之后,锁上门,你就不能进去了,毕竟俩人上厕所挺尴尬的......

Python 哪些操作是线程安全的?

1.一个操作可以在多线程环境中安全使用,获取正确的结果。

2.线程安全的操作好比线程是顺序执行而不是并发执行的,比如(i += 1)操作就不是线程安全的,因为它在字节码上的操作是多个操作,先进行加一,再赋值给 i ,所以非常容易在执行某一步的时候切换到其他操作上去。

3.一般如果涉及到写操作需要考虑如何让多个线程安全访问数据。我们知道大家都读数据,这个没什么问题。但是涉及到写的时候,就会有竞争。

那么我们如何操作会让线程安全呢?可以通过下面的方式:

1.4.2.2线程同步的方式

1.首先我们可以通过加互斥锁的形式,有效的防止多个线程同时访问公共资源。

import threading

lock = threading.Lock()

n = [0]

def foo():
    with lock:
        n[0] = n[0] + 1
        n[0] = n[0] + 1

threads = []
for i in range(5000):
    t = threading.Thread(target=foo)
    threads.append(t)

for t in threads:
    t.start()

print(n)

2.可以使用信号量(Semphare),它控制同一时刻多个线程访问同一个资源的线程数。

信号量也是在 threading 模块下

3.最后一个便是事件(Event),通过通知的方式保持多个线程同步。

事件是 threading.Event

1.4.2.3进程间的通信方式

Inter-Process Communication 进程间传递信号或者数据

1.管道/匿名管道/有名管道(pipe)

2.信号(Signal):其实这个大家并不陌生,只是没有听过这种叫法而已。我们是用 Ctrl + c 去杀死进程的时候,就是给程序发送了一个 SIGINT 的程序终止信号。

3.消息队列(Message):比如常用的卡夫卡

下面还有几种方式,但是我们不会用到:

4.共享内存(share memory):操作系统为我们提供了一块共享内存,不同的进程可以去读写它。

5.信号量(Semaphore)

6.套接字(socket):最常用的方式,我们的 web 应用都是这种方式

1.4.2.4 Python 如何使用多线程

threading模块

1.threading.Thread 类用来创建线程

2.start() 方法启动线程

3.可以用 join() 等待线程结束

1.4.2.5 Python 如何使用多进程

Python 有 GIL ,可以用多进程实现 CPU 密集程序

1.multiprocessing 多进程模块

2.Multiprocessing.Process 类实现多进程

3.一般用在 CPU 密集程序里,避免 GIL 的影响

比如我们常用的 Gunicorn 和 uWSGI 就是通过 master-slave 这种方式启动多个进程来响应客户请求

下面我们使用一个计算斐波那契数列的例子来说明一下:

import multiprocessing

def fib(n):
    """worker function"""
    if n <= 1:
        return 1
    return fib(n-1) + fib(n-2)

if __name__ == '__main__':
    jobs = []
    for i in range(10, 20):
        p = multiprocessing.Process(target=fib, args=(i,))
        jobs.append(p)
        p.start()

1.4.3操作系统内存管理机制

现代化的编程语言一般都有垃圾回收机制,因此使用 Python 我们很少去关注内存,但是 C/C++ 等则经常需要自己去操作内存,一旦忘记了就会发生内存泄漏。

1.4.3.1分页机制

操作系统为了高效管理内存,减少碎片,就采用了分页机制,把逻辑地址和物理地址进行分离。

逻辑地址和物理地址分离的内存分配管理方案:

1.程序的逻辑地址划分为固定大小的页(Page)

2.物理地址划分为同样大小的帧(Frame)

3.通过页表对应逻辑地址和物理地址

下面我们通过一张图看一下什么是分页机制:

我们可以发现通过页表将物理地址和逻辑地址做了个映射,而且内存块可以是不连续的,可以横跨一些内存块,很灵活。

1.4.3.2分段机制

分段是为了满足代码的一些逻辑需求

1.数据共享,数据保护,动态链接等

2.通过段表实现逻辑地址和物理地址的映射关系

3.每个段内部是连续内存分配,段和段之间是离散分配的

下面我们通过一张图片来看一下段表的分配机制:

1.4.3.3分页 vs 分段

1.页是出于内存利用率的角度提出的离散分配机制

2.段是通过用户角度,用于数据保护、数据隔离等用途的管理机制

3.页的大小是固定的,操作系统决定;段大小不确定,用户程序决定

1.4.3.4什么是虚拟内存

通过把一部分暂时不用的内存信息放到硬盘上

1.局部性原理:程序运行时候只有部分必要的信息装入内存

时间局部性:一块内存如果被访问,那么在不久的将来它可能还会被访问 空间局部性:一块内存被访问,那么它周围的内存也很有可能被访问

2.内存中暂时不需要的内容放到硬盘上

3.系统似乎提供了比实际内存大得多的容量,称之为虚拟内存

1.4.3.4什么是内存抖动(颠簸)

本质上是频繁的页调度行为。频繁的页调度,进程不断产生缺页中断。

简单的进行一个解释。当交换内存到硬盘的时候,需要某种策略,常用的有 LRU 、 LFU 或者是先进先出。但是策略如果使用不当的话,就容易导致一个问题,比如刚置换了一个页,立马又需要它,然后将它再交换回来,折腾不?进程产生的就是缺页中断,对于用户来说就是电脑性能急剧下降。

那么怎么解决呢?在运行程序过多的时候,我们可以杀掉一些无关的进程,或者增加服务器的物理内存。对于开发者来说,我们编写一些进程或者内存中的一些缓存的时候,比如 Redis ,必须要保证有足够好的剔除策略,比如 LRU 或者 LFU。Redis 内部其实是支持 LRU 的策略的,当我们使用的内存已经超出了设定的内存,它就会使用 LRU 策略进行剔除。

1.4.3.5 Python 的垃圾回收机制原理

Python 以引用计数为主解决垃圾回收的问题,但是循环引用的问题是无法解决的。然后就引入了标记清除和分代回收解决上面提到的问题。概括一下就是 Python 的垃圾回收机制是引用计数为主,标记清除和分代回收为辅。

Python 解释器只会在引用计数为 0 的时候回收对象。那么下面我们看一下具体细节:

什么时候引用计数增加呢?

1.对象创建的时候,a = 1

2.对象被引用的时候,b = a

3.对象作为参数传递 func(a)

4.对象存储在容器中 l = [a]

什么时候引用计数会减少呢?

1.显式使用 del a

2.引用指向了别的对象 b = None

3.离开对象的作用域(比如函数执行结束)

4.从一个容器移除对象或者销毁容器

我们可以使用下面的命令查看对象的引用计数:

sys.getrefcount(对象)

当你去查看一些特殊对象的引用计数的时候,也许会发生一些诡异的现象,比如用上述函数去查看 1,你会发现引用计数超大?不要怀疑你的眼睛,其实这是正常现象。它和 Python 的底层实现有关,Python 底层有小整数对象池,很多地方都引用了它,不光是 1,它缓存了 [-5, 256] ,感兴趣的可以了解一下,不光是小整数哦~它还有字符串,默认是 20 位。

一开始我们就说过了引用计数虽然好,但是无法解决循环引用的问题,它会导致内存泄漏,这是致命的。下面我们简单的将一下标记清除算法的原理:

首先我们从垃圾回收的根对象开始,找到可达对象,也就是图中用箭头连接的对象。将连接的这些对象标记成绿色,不能到达的点标记为灰色,系统就认为没人再引用他们了,然后将其删除。

根对象一般就是当前栈里面的一些对象。

什么又是分代回收呢?

其实 Python 把对象的生命周期分为了 3 代。刚创建的一些新对象称为第 0 代,每隔一段时间 Python 就会针对第 0 代、第 1 代和第 2 代执行刚才提到的标记清除回收。他们都是有阈值的,也就是每隔多少时间去清除第 0 代等等。在每一次执行完标记回收之后,会把那些没有被回收的对象放入下一代。

我们可以通过下面的命令查看:

import gc
gc.get_threshold()

返回一个三个数的元组,第一个数字代表的是阈值,也就是当对象达到阈值的时候执行一次回收。详细介绍可以查看之前的文章『我是个链接』,文章底部为垃圾回收详细介绍。

优质文章推荐:

redis操作命令总结

MySQL相关操作

SQL查询语句

前端中那些让你头疼的英文单词

Flask框架重点知识总结回顾

团队开发注意事项

浅谈密码加密

Django框架中的英文单词

Django中数据库的相关操作

DRF框架中的英文单词

DRF框架

Django相关知识点回顾

python技术面试题-腾讯

原文发布于微信公众号 - 小闫笔记(Pythonnote)

原文发表时间:2019-06-21

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券