每日分享

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(软链接和硬链接)等。

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 杀死进程

3.top/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 调度和分配的基本单位,实现进程内并发

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),它控制同一时刻多个线程访问同一个资源的线程数。

3.最后一个便是事件(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 的影响

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

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(对象)

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

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

什么又是分代回收呢?

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

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

import gc
gc.get_threshold()

返回一个三个数的元组,第一个数字代表的是阈值,也就是当对象达到阈值的时候执行一次回收。

01-16 06:40
查看更多