Python基础、多线程多进程学习
基础部分学习
字符串
1 | str = '10strbcd9 ' |
见上述demo中 定义了一个字符串str 及字符串常用的函数。由引号(单引号、双引号、三对引号)包裹的字符、数字均为字符串。
| 函数名 | 作用 | 输出 |
|---|---|---|
| [:] | 切片操作 | 根据索引截取所需字符串 |
| index() | 查找字符串中是否存在某个元素 | 有则返回第一次出现的索引,无则报错 |
| find() | 查找字符串中是否存在某个元素 | 有则返回第一次出现的索引,无则返回 -1 |
| split() | 字符串去空操作 | 去除字符串中的空白符,并返还列表(若字符串中间存在空白符,则返回列表拥有两元素) |
| split() | 字符串去空操作 | 也可制定去除的符号,如’-a-b-c-‘,则开头两端返回 [‘’, ‘a’, ‘b’, ‘c’, ‘’] |
上述代码运行如下图所示
列表
列表是Python中最基础的数据结构a = ['a','b',['aaa']],其中被[]包裹的内容为列表中的元素,含有3个元素分别为a、 b和列表['aaa']。列表中可嵌套列表、元组、字典、集合等数据结构。
1 | b = ['a','b',['aaa'],{'a':1,'b':'这是字段'},('这是元组',),{'a','b','这是集合'}] |
上述变量b也为列表。
1 | x = ['a','b',['aaa']] |
上述demo运行如图所示,列表常用函数均已注释说明用法及区别

元组
特性:与上述介绍的字符串一致,均为不可变的数据结构。其他处与列表类似,元组使用小括号,列表使用方括号。
元组创建很简单,只需要在括号中添加元素,并使用逗号隔开即可。若元组只有一个元素需以('元组',)这种形式出现,单个元素后跟一个逗号,否则Python解释器默认其为 ('不是元组') 字符串类型

由于元组为不可变类型的数据结构,所以其函数只有通用的几个
| 函数 | 作用 | 输出 |
|---|---|---|
| min() | 比较元组中元素大小,返回最小的元素 | 元组中最小的元素 |
| max() | 比较元组中元素大小,返回最大的元素 | 元组中最大的元素 |
| tuple() | 将列表、字符串转换为元组 | 列表元素不变输出元组,字符串单个字符为元素输出元组 |
测试demo如下
1 | a = ['aaaa','b'] |

字典
字典是另一种可变容器模型,且可存储任意类型对象,且字典无序。其中字典的表现格式为{ key : value }的格式,其中key的值保持唯一性,若重复,则后续key的value值会覆盖之前的value ,如demo为a = {'a':1,'a':2},打印结果为:{'a': 2},会直接覆盖掉第一个键名为a的键值对。
字典常见函数如下表:
| 函数名 | 作用 | 输出 |
|---|---|---|
| pop() | 根据Key名删除键值对 | 返回删除的value |
| del dict[keyname] | 根据Key名删除键值对 | 无返回值 |
| dict[‘key’] | 若此前为引用的key名,则增加元素。引用过key名则修改value值 | |
| dict.get(‘keyname’) | 根据key名找value | 存在则输出value,反之输出None |
| dict.keys() | 输出字典中所有key的列表集合对象 dict_keys | |
| dict.values() | 输出字典中所有value的列表集合对象 dict_values | |
| dict.items() | 输出字典中所有键值对的集合对象 dict_items |
其中可将返回的 dict_keys dict_values dict_items三种类型对象看作一个迭代器,循环即可获得该值
演示demo如下:
1 | a = { "a":1,"a":2,'c':3 } |
输出如图
集合
集合为无序且不可重复元素的数据结构,集合大多数使用情况为去重 set(具有重复元素的列表),无太多使用说明。
集合的表达方式为: {a,b,c}采用大括号的方式
常用函数补充
join()函数
可使用该函数将列表的元素拼接为字符串
1
2a = ['a','b','c']
str = ''.join(a)print(str)的结果为abc,也可使用str = ' '.join(a),print(str)的打印结果为a b czip()函数
1 | a = ['a','b','c'] |
- 列表推导式学习
1 | a = [x for x in range(10) if x % 9 == 0] |
优点 :简化代码量。
多线程/进程的学习
多线程-threading库简单学习
可通过函数定义方法的模式引用多线程
简单代码块引用
1 | def print_name(name): |
可通过重写Thread类引用多线程
简单代码块引用
1 | class MyThread(Thread): |
join方法
上述引用代码在执行时输出均为以下执行效果
1 | 主线程start |
如图
当t1与t2线程调用start()方法后,打印出了主线程end,但是t1 t2并没有执行完,还在继续执行输出。注意:并不是打印了主线程end 就是主线程结束了,只是主线程没有等t1 t2线程结束,仍然在继续运行主线程
如果需要 t1 t2执行完成在继续执行主线程,可以调用 join()方法。代码块如下
1 | if __name__ == '__main__': |
增加t1 t2调用join()方法后执行效果如下
会需要当两个子进程t1 t2结束后,主线程才会继续运行
守护进程
守护进程为 主进程结束后,守护进程也直接结束。使用方法为 线程名.daemon = True设置该线程对象的daemon属性为True如图

当主线程打印 主线程end后 守护线程t1也就被结束了。
这个时候有一个需要明确的重要的点,如下代码块
1 | if __name__ == '__main__': |
两个子线程,但t1为守护线程,t2并没有守护线程的属性。所以该代码在运行时,执行效果如下
当主线程打印出 主线程end后,但守护进程t1仍然在继续执行。所以主线程的真正结束时间是: 所有非守护子线程运行结束后,才会结束主线程,同理守护子线程也会结束。因为非守护进程t2没有结束,所以主进程未结束,所以t1进程在主线程打印出主线程end后仍继续执行。
互斥锁及应用场景学习
无互斥锁demo如下
1 | class 账户: |
定义了两个类 账户类中说明了账户金额和账户的户主,取钱类会判断当前取钱的取款人,预取金额,取钱的账户和取款人。直接用下列代码调用该demo时会存在一个问题
1 | if __name__ == '__main__': |

两个线程同时访问账户的金额资源,账户余额会变成-20.很显然在生产环境是很可能遭遇到该情况,所以就需要引进一个概念,对访问资源加上锁。调用threading模块的Lock()方法,对资源加锁处理
互斥锁使用方法为:
1 | 锁 = Lock() #初始化 Lock() |
原有demo加上锁后执行效果如下
当对该资源加锁后,避免了其他线程在使用中被人修改资源属性。
信号量的学习与场景
在互斥锁Lock()的学习使用中,可以限制资源只允许被一个线程访问,当该资源应该控制访问线程数量为一个区间时,互斥锁就无法满足需求了。引入新概念信号量 Semaphore()的学习。
demo如下
1 | def home(name): #定义一个只能容纳2人的房子 |
运行该demo后输出效果如图
当该资源被两个线程访问时,其他线程均需等待。限制访问数量由x = Semaphore(2)决定。演示demo设置为2
生产者消费者queue学习
引入新的概念如图
将线程分为生产者、消费者两类。一类生产数据、一类消费数据。引入新的Python库queue
用法如下:
- 引入
queue模块的Queue类 - 初始化该类
q = Queue() - 使用
q.put()对资源池进行添加资源的操作 - 使用
q.get()从资源池中取出资源
演示demo如下:
1 | def 生产者(): |
使用生产者与消费者的模式进行线程之间的通信。因为多线程特征为**共享堆、不共享栈**,可以把这句话理解为 共享全局资源、不共享函数内资源。demo中的列表name_list如果采用多线程的方式只属于调用该函数的线程独有,其他子线程无法访问。运行上述demo输出如图
但是如果不适用queue采用消费者的模式,直接让线程t2访问name_list资源的话如图
Pycharm直接报错,当然可以将name_list直接设置为全局变量,但如果场景为爬虫,url_list对应全局变量,存储需要爬取或爆破的url,但需要使用多线程爬访问爬取url的html页面,将生产者()函数定义为爬取,将生产者()函数定义为消费者,负责解析爬取回来的数据。这个时候就需要用到queue模式,让其中一个线程可以消费到另一个函数产生的值。
多进程multiprocessing库的学习
多线程与多进程的概念区别
多线程比多进程节省资源,但是部分场景下多进程比多线程效率高。
多进程的特点:进程与进程之间既不共享堆、也不共享栈。
函数定义模式与重写Process类模式调用多进程
函数定义模式调用多进程demo如下
1 | def print_name(name): |
基本上看与多线程的使用方法一致,但通过引用os库的getpid()与getppid()的方法输出子进程pid号与父进程pid号可以看出区别,该demo输出如下图

三个子进程输出的父进程pid均为18336,但三个子进程的pid均不相同。子进程都是由父进程创建的。同理
重写Process()模式调用多进程demo如下:
1 | from multiprocessing import Process |
输出如下
函数调用与**重写Process()**的多进程使用方法基本与多线程一致,均为创建进城后需使用start()方法启动进程,均可使用join()方法使主进程等待子进程运行完成后再继续下一步运行。
区别点:生产者消费者模式
多进程与多线程在我们初阶小白水平看来,只有一个区别堆和栈的资源均不共享。
之前的queue在此处也无法实现两个子进程之间的通信了,需直接使用multiprocessing库中的Queue类进行多进程之间的通信。
**错误示范-引用queue模块中的Queue()**尝试多进程之间的通信
1 | from multiprocessing import Process |
该错误示范运行时报错截图如下
正确demo如下
1 | from multiprocessing import Process,Queue |
此处,与多进程区别在于,需要将 q = Queue()当作参数发送给进程,且函数需要接收该参数才可以。运行截图如下

线程池、进程池的使用
优点
比手动定义多线程、多进程简单方便。线程/进程数量有限,不会无限制创建线程和进程数量。可获得函数执行后的结果
测试demo如下:
1 | from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor,wait |
运行后输出如图

可直接获得函数的返回值,也可以利用 concurrent.futures中的wait()函数来等待线程池、进程池中的线程/进程结束完后在继续执行主进程/线程,等同于 多线程/进程中的join()方法。同时可以使用ThreadPoolExecutor/ProcessPoolExecutor的map()方法直接传参为函数名,参数列表即可。但缺点为不支持直接调用wait()函数。
