#转载请联系

1.什么是线程?

进程是操作系统分配程序执行资源的单位,而线程是进程的一个实体,是CPU调度和分配的单位。一个进程肯定有一个主线程,我们可以在一个进程里创建多个线程来实现多任务。

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

2.一个程序实现多任务的方法

如上图所示,实现多任务,我们可以用几种方法。

(1)在主进程里面开启多个子进程,主进程和多个子进程一起处理任务。

有关多个进程实现多任务,可以参考我的博文:https://www.cnblogs.com/chichung/p/9532962.html

(2)在主进程里开启多个子线程,主线程和多个子线程一起处理任务。

(3)在主进程里开启多个协程,多个协程一起处理任务。

有关多个协程实现多任务,可以参考我的博文:https://www.cnblogs.com/chichung/p/9544566.html

注意:因为用多个线程一起处理任务,会产生线程安全问题,所以在开发中一般使用多进程+多协程来实现多任务。

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

3.多线程的创建方式

import threading
p1 = threading.Thread(target=[函数名],args=([要传入函数的参数]))
p1.start() # 启动p1线程

我们来模拟一下多线程实现多任务。

假如你在用网易云音乐一边听歌一边下载。网易云音乐就是一个进程。假设网易云音乐内部程序是用多线程来实现多任务的,网易云音乐开两个子线程。一个用来缓存音乐,用于现在的播放。一个用来下载用户要下载的音乐的。这时候的代码框架是这样的:

import threading
import time def listen_music(name):
while True:
time.sleep(1)
print(name,"正在播放音乐") def download_music(name):
while True:
time.sleep(2)
print(name,"正在下载音乐") if __name__ == '__main__':
p1 = threading.Thread(target=listen_music,args=("网易云音乐",))
p2 = threading.Thread(target=download_music,args=("网易云音乐",))
p1.start()
p2.start() 输出:
网易云音乐 正在播放音乐
网易云音乐 正在下载音乐
网易云音乐 正在播放音乐
网易云音乐 正在播放音乐
网易云音乐 正在下载音乐
网易云音乐 正在播放音乐
网易云音乐 正在播放音乐
网易云音乐 正在下载音乐
网易云音乐 正在播放音乐
网易云音乐 正在播放音乐
网易云音乐 正在播放音乐
......
......

观察上面的输出代码可以知道:

1.CPU是按照时间片轮询的方式来执行子线程的。cpu内部会合理分配时间片。时间片到a程序的时候,a程序如果在休眠,就会自动切换到b程序。

2.严谨来说,CPU在某个时间点,只在执行一个任务,但是由于CPU运行速度和切换速度快,因为看起来像多个任务在一起执行而已。

除了上面的方法创建线程,还有另一种方法。可以编写一个类,继承threaing.Thread类,然后重写父类的run方法。

import threading
import time class MyThread(threading.Thread):
def run(self):
for i in range(5):
time.sleep(1)
print(self.name,i) t1 = MyThread()
t2 = MyThread()
t3 = MyThread()
t1.start()
t2.start()
t3.start() 输出:
Thread-1 0
Thread-3 0
Thread-2 0
Thread-1 1
Thread-2 1
Thread-3 1
Thread-1 2
Thread-3 2
Thread-2 2
Thread-1 3
Thread-2 3
Thread-3 3
Thread-1 4
Thread-2 4
Thread-3 4

运行时无序的,说明已经启用了多任务。

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

4.线程何时开启,何时结束

(1)子线程何时开启,何时运行
当调用thread.start()时 开启线程,再运行线程的代码
(2)子线程何时结束
子线程把target指向的函数中的语句执行完毕后,或者线程中的run函数代码执行完毕后,立即结束当前子线程
(3)查看当前线程数量
通过threading.enumerate()可枚举当前运行的所有线程
(4)主线程何时结束
所有子线程执行完毕后,主线程才结束
import threading
import time def run():
for i in range(5):
time.sleep(1)
print(i) t1 = threading.Thread(target=run)
t1.start()
print("我会在哪里出现") 输出:
我会在哪里出现
0
1
2
3
4

为什么主进程(主线程)的代码会先出现呢?因为CPU采用时间片轮询的方式,如果轮询到子线程,发现他要休眠1s,他会先去运行主线程。所以说CPU的时间片轮询方式可以保证CPU的最佳运行。

那如果我想主进程输出的那句话运行在结尾呢?该怎么办呢?这时候就需要用到 join() 方法了。

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

5.线程的 join() 方法

import threading
import time def run():
for i in range(5):
time.sleep(1)
print(i) t1 = threading.Thread(target=run)
t1.start()
t1.join()
print("我会在哪里出现") 输出:
0
1
2
3
4
我会在哪里出现

join() 方法可以阻塞主进程(注意只能阻塞主进程,其他子进程是不能阻塞的),直到 t1 子线程执行完,再解阻塞。

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

6.线程可以共享全局变量

这个稍微实验下就可以知道了,所以这里不废话。

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

7.多线程共享全局变量出现的问题

我们开两个子线程,全局变量是0,我们每个线程对他自加1,每个线程加一百万次,这时候就会出现问题了,来,看代码:

 import threading
import time num = 0 def work1(loop):
global num
for i in range(loop):
# 等价于 num += 1
temp = num
num = temp + 1
print(num) def work2(loop):
global num
for i in range(loop):
# 等价于 num += 1
temp = num
num = temp + 1
print(num) if __name__ == '__main__':
t1 = threading.Thread(target=work1,args=(1000000,))
t2 = threading.Thread(target=work2, args=(1000000,))
t1.start()
t2.start() while len(threading.enumerate()) != 1:
time.sleep(1)
print(num) 输出:
1459526 # 第一个子线程结束后全局变量一共加到这个数
1588806 # 第二个子线程结束后全局变量一共加到这个数
1588806 # 两个线程都结束后,全局变量一共加到这个数

奇怪了,我不是每个线程都自加一百万次吗?照理来说,应该最后的结果是200万才对的呀。问题出在哪里呢?

我们知道CPU是采用时间片轮询的方式进行几个线程的执行。

假设我CPU先轮询到work1(),num此时为100,在我运行到第10行时,时间结束了!此时,赋值了,但是还没有自加!即temp=100,num=100。

然后,时间片轮询到了work2(),进行赋值自加。num=101了。

又回到work1()的断点处,num=temp+1,temp=100,所以num=101。

就这样!num少了一次自加!

在次数多了之后,这样的错误积累在一起,结果只得到158806!

这就是线程安全问题

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

8.互斥锁可以弥补部分线程安全问题。(互斥锁和GIL锁是不一样的东西!)

当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制

线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。

互斥锁为资源引入一个状态:锁定/非锁定

某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。

互斥锁有三个常用步骤

lock = threading.Lock()  # 取得锁
lock.acquire() # 上锁
lock.release() # 解锁

下面让我们用互斥锁来解决上面例子的线程安全问题。

import threading
import time num = 0
lock = threading.Lock() # 取得锁
def work1(loop):
global num
for i in range(loop):
# 等价于 num += 1
lock.acquire() # 上锁
temp = num
num = temp + 1
lock.release() # 解锁
print(num) def work2(loop):
global num
for i in range(loop):
# 等价于 num += 1
lock.acquire() # 上锁
temp = num
num = temp + 1
lock.release() # 解锁
print(num) if __name__ == '__main__':
t1 = threading.Thread(target=work1,args=(1000000,))
t2 = threading.Thread(target=work2, args=(1000000,))
t1.start()
t2.start() while len(threading.enumerate()) != 1:
time.sleep(1)
print(num) 输出:
1945267 # 第一个子线程结束后全局变量一共加到这个数
2000000 # 第二个子线程结束后全局变量一共加到这个数
2000000 # 两个线程都结束后,全局变量一共加到这个数

python多线程实现多任务的更多相关文章

  1. python 多线程就这么简单(转)

    多线程和多进程是什么自行google补脑 对于python 多线程的理解,我花了很长时间,搜索的大部份文章都不够通俗易懂.所以,这里力图用简单的例子,让你对多线程有个初步的认识. 单线程 在好些年前的 ...

  2. Python多线程及其使用方法

    [Python之旅]第六篇(三):Python多线程及其使用方法   python 多线程 多线程使用方法 GIL 摘要: 1.Python中的多线程     执行一个程序,即在操作系统中开启了一个进 ...

  3. 搞定python多线程和多进程

    1 概念梳理: 1.1 线程 1.1.1 什么是线程 线程是操作系统能够进行运算调度的最小单位.它被包含在进程之中,是进程中的实际运作单位.一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发 ...

  4. 进程,线程,GIL,Python多线程,生产者消费者模型都是什么鬼

    1. 操作系统基本知识,进程,线程 CPU是计算机的核心,承担了所有的计算任务: 操作系统是计算机的管理者,它负责任务的调度.资源的分配和管理,统领整个计算机硬件:那么操作系统是如何进行任务调度的呢? ...

  5. python多线程和多进程

    1 概念梳理: 1.1 线程 1.1.1 什么是线程 线程是操作系统能够进行运算调度的最小单位.它被包含在进程之中,是进程中的实际运作单位.一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发 ...

  6. 【Python】关于Python多线程的一篇文章转载

    猪哥推荐的学习网址 http://www.jb51.net/article/110164.htm yeayee ------>更多技巧------>更多源码------>http:/ ...

  7. python 多线程示例

    原文链接:http://www.cnblogs.com/whatisfantasy/p/6440585.html 1 概念梳理: 1.1 线程 1.1.1 什么是线程 线程是操作系统能够进行运算调度的 ...

  8. python 多线程实现

    多线程和多进程是什么自行google补脑 对于python 多线程的理解,我花了很长时间,搜索的大部份文章都不够通俗易懂.所以,这里力图用简单的例子,让你对多线程有个初步的认识. 单线程 在好些年前的 ...

  9. [转]python 多线程threading简单分析

    多线程和多进程是什么自行google补脑 对于python 多线程的理解,我花了很长时间,搜索的大部份文章都不够通俗易懂.所以,这里力图用简单的例子,让你对多线程有个初步的认识. 单线程 在好些年前的 ...

随机推荐

  1. Postmortem Report 第一轮迭代事后分析报告

    一.设想和目标 1.1 我们的软件要解决什么问题?是否定义得很清楚?是否对典型用户和典型场景有清晰的描述? 我们的软件<BlueZ>是一款全新动作类塔防游戏.与市面上已经存在的塔防游戏不同 ...

  2. Runnable、Callable、Future和CompletableFuture

    一.Runnable Runnable非常简单,只需要实现一个run方法即可,没有参数,也没有返回值.可以以new Thread的方式去运行,当然更好的方式在放到excutorPool中去运行. 二. ...

  3. bcc编译

    bcc编译,直接在docker里编,太方便:第一次深切体会到docker的强大: 1)下载bcc源码: 2) 把源码中的Dockerfile.ubuntu重命名为Dockerfile 3)sudo d ...

  4. Redis集群操作手册

    一.原始集群(6节点 3主3从): (1)启动集群: [root@bhz004 ~]# /usr/local/redis/bin/redis-server /usr/local/redis-clust ...

  5. 集群hadoop ubuntu版

    搭建ubuntu版hadoop集群 用到的工具:VMware.hadoop-2.7.2.tar.jdk-8u65-linux-x64.tar.ubuntu-16.04-desktop-amd64.is ...

  6. python实现关联规则

    代码中Ci表示候选频繁i项集,Li表示符合条件的频繁i项集 # coding=utf-8 def createC1(dataSet): # 构建所有1项候选项集的集合 C1 = [] for tran ...

  7. 设计一个JavaScript框架需要编写哪些模块

    在这个js框架随处乱跑的时代,你是否考虑过写一个自己的框架?下面的内容也许会有点帮助. 一个框架应该包含哪些内容? 1. 语言扩展 大部分现有的框架都提供了这部分内容,语言扩展应当是以ECMAScri ...

  8. Mysql History list length 值太大引起的问题

    1. 环境 Mysql 主从 Mysql版本:5.1.49-log 系统:Red Hat Enterprise Linux Server release 5.4  64bit 2. 表面现象 数据库操 ...

  9. init_connect基本用法

    服务器为每个连接的客户端执行的字符串.字符串由一个或多个SQL语句组成.要想指定多个语句,用分号间隔开.例如,每个客户端开始时默认启用autocommit模式.没有全局服务器变量可以规定autocom ...

  10. 如何快速的进行sql脚本升级

    sql脚本升级即从一个老的脚本升级到一个新的全量的脚本.比如公司有某一个项目,有的客户已经用这个项目了,数据库里面去掉以前的初始化数据外,现在还有了客户自己的数据.但是这个版本中有严重的bug,所以为 ...