多线程(multithreaded,MT),是指从软件或者硬件上实现多个线程并发执行的技术

什么是进程?

计算机程序只不过是磁盘中可执行的二进制(或其他类型)的数据。它们只有在被读取到内存中,被操作系统调用的时候才开始它们的生命周期。

进程是程序的一次执行。每个进程都有自己的地址空间、内存、数据栈及其他运行轨迹的辅助数据。比如打开播放器就是一个进程。

什么是线程?

线程跟进程相似,不同的是所有的线程运行在同一个进程中,共享相同的运行环境,共享同一片数据空间。比如在播放器中一边播放画面,一遍播放声音,就是多个线程在同时执行。线程有开始,顺序执行和结束三部分。实际上,只有多核CPU才能实现真正意义上的多线程,在一个CPU上,在同一时刻只能执行一个线程。但是即便处理器只能运行一个线程,操作系统也可以通过快速的在不同线程之间进行切换,由于时间间隔很小,来给用户造成一种多个线程同时运行的假象。

线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

ps:如果大家还不是很了解进程与线程,可以看下廖雪峰老师的文章 ,写的非常棒!

参考地址:http://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/0014319272686365ec7ceaeca33428c914edf8f70cca383000

单线程与多线程实例感受

假设:loop0() 执行需要4s,loop1()执行需要2s。

单线程代码实例:

#!/usr/bin/env python
# -*- coding: utf-8 -*- from time import sleep, ctime def loop0():
print "loop 0 start at:%s" % ctime()
sleep(4)
print "loop 0 done at:%s" % ctime() def loop1():
print "loop 1 start at:%s" % ctime()
sleep(2)
print "loop 1 done at:%s" % ctime() def main():
loop0()
loop1() if __name__ == '__main__':
print "main program start at:%s" % ctime()
main()
print "main program all done at:%s" % ctime()

输出结果如下:

main program start at:Sat Oct 22 15:35:32 2016
loop 0 start at:Sat Oct 22 15:35:32 2016
loop 0 done at:Sat Oct 22 15:35:36 2016
loop 1 start at:Sat Oct 22 15:35:36 2016
loop 1 done at:Sat Oct 22 15:35:38 2016
main program all done at:Sat Oct 22 15:35:38 2016

从结果中可分析得知,loop0与loop1串行执行,先完成loop0,在执行loop1.总共花费了4s+2s=6s

多线程代码实例:

#!/usr/bin/env python
# -*- coding: utf-8 -*- from time import sleep, ctime
import thread def loop0():
print "loop 0 start at:%s" % ctime()
sleep(4)
print "loop 0 done at:%s" % ctime() def loop1():
print "loop 1 start at:%s" % ctime()
sleep(2)
print "loop 1 done at:%s" % ctime() def main():
thread.start_new_thread(loop0, ())
thread.start_new_thread(loop1, ())
sleep(4) if __name__ == '__main__':
print "main program start at:%s" % ctime()
main()
print "main program all done at:%s" % ctime()

输出结果如下:

main program start at:Sat Oct 22 15:44:06 2016
loop 0 start at:Sat Oct 22 15:44:06 2016
loop 1 start at:Sat Oct 22 15:44:06 2016
loop 1 done at:Sat Oct 22 15:44:08 2016
mian program all done at:Sat Oct 22 15:44:10 2016
loop 0 done at:Sat Oct 22 15:44:10 2016

从结果中可分析得知,loop0与loop1并行执行,总共花的时间为最长线程的时间4s

多线程实现方式介绍

python主要提供了三个模块thread、threading和Queue 来支持python的多线程操作。thread 和 threading模块允许程序员创建和管理线程,Queue模块允许用户创建一个可以用于多线程之间共享数据的队列数据结构。

一般现在我们只使用threading模块来编程了,thread模块定义了很多原始行为,更接近底层,而threading模块抽象了thread模块可用性更好,同时提供更多特性。另外一个避免使用thread模块的原因是,thread不支持守护线程,当主线程退出时,所有的子线程无论他们是否还在工作,都会被强行退出。

threading模块

threading的Thread类是主要的运行对象。他有很多thrad模块里没有的函数,如下表:

 
函数 描述
start() 开始线程的执行
run() 定义线程的功能的函数(一般会被子类重写)
join(timeout=None) 程序挂起,直到该子线程结束再继续执行主线程;如果给了timeout,则最多阻塞timeout秒
getName() 返回线程的名字
setName() 设置线程的名字
isAlive() 布尔标志,表示这个线程是否还在运行中
isDaemon() 返回线程的daemon标志
setDaemon(daemonic)                 把线程的daemon标志设为daemonic(一定要在调用start()函数前调用)

使用threading模块的Thread类创建线程,有三种比较相像的方法:

  • 创建一个Thread的实例,传给它一个函数;
  • 创建一个Thread的实例,传给它一个可调用的类对象
  • 从Thread派生出一个子类,创建一个这个子类的实例。(现在创建线程的通用方法一般是创建一个类并且继承threading.Thread,然后重写其中的__init__和run()方法)

创建多线程的方式

  • 实例一:创建一个Thread的实例,传给它一个函数
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
多线程在后台执行,与主线程并行运行
join() 等待子线程停止,才继续往下执行主线程
"""
import threading
from time import sleep, ctime def work(nloop, nsec):
print "Thread-%s start working at %s \n" % (nloop, ctime())
sleep(nsec)
print 'Thread-%s finished background working at %s \n' % (nloop, ctime()) if __name__ == '__main__':
print "\033[1;31;40m The main program start to run in foreground at %s\033[0m \n" % ctime()
sleep_sec = [2, 4]
loop_times = len(sleep_sec)
threads_list = [] for i in range(loop_times): # create threads
t = threading.Thread(target=work, args=(i, sleep_sec[i])) # 创建一个Thread实例,传给它一个函数
threads_list.append(t) for i in range(loop_times): # start threads
threads_list[i].start() print "\033[1;31;40m The main program continues to run in foreground!\033[0m \n" for i in range(loop_times): # wait for all threads to finish
threads_list[i].join() print '\033[1;31;40m Main program waited until background was done at %s\033[0m \n' % ctime()

实例一输出结果如下:

  • 实例二:创建一个Thread的实例,传给它一个可调用的类对象
#!/usr/bin/env python
# -*- coding: utf-8 -*- import threading
from time import sleep, ctime class ThreadFunc(object):
def __init__(self, func, args):
self.func = func
self.args = args def __call__(self):
apply(self.func, self.args) def work(nloop, nsec):
print "Thread-%s start working at %s \n" % (nloop, ctime())
sleep(nsec)
print 'Thread-%s finished background working at %s \n' % (nloop, ctime()) if __name__ == '__main__':
print "\033[1;31;40m The main program start to run in foreground at %s\033[0m \n" % ctime()
sleep_sec = [2, 4]
loop_times = len(sleep_sec)
threads_list = [] for i in range(loop_times): # create threads
t = threading.Thread(target=ThreadFunc(work, (i, sleep_sec[i]))) # 创建一个Thread实例,传给它一个可调用的类对象
threads_list.append(t) for i in range(loop_times): # start threads
threads_list[i].start() print "\033[1;31;40m The main program continues to run in foreground!\033[0m \n" for i in range(loop_times): # wait for all threads to finish
threads_list[i].join() print '\033[1;31;40m Main program waited until background was done at %s\033[0m \n' % ctime()

实例二输出结果如下:

  • 实例三:从Thread派生出一个子类,创建一个这个子类的实例。
#!/usr/bin/env python
# -*- coding: utf-8 -*- import threading
from time import sleep, ctime class AsyncWork(threading.Thread):
def __init__(self, nsec):
super(AsyncWork, self).__init__()
self.nsec = nsec def work(self):
print "%s start working at %s \n" % (self.getName(), ctime())
sleep(self.nsec)
print '%s finished background working at %s \n' % (self.getName(), ctime()) def run(self):
self.work() if __name__ == '__main__':
print "\033[1;31;40m The main program start to run in foreground at %s\033[0m \n" % ctime()
sleep_nsec = [2, 4]
loop_times = len(sleep_nsec)
threads_list = [] for i in range(loop_times): # create threads
t = AsyncWork(sleep_nsec[i]) # 创建一个Thread子类的实例
threads_list.append(t) for i in range(loop_times): # start threads
threads_list[i].start() print "\033[1;31;40m The main program continues to run in foreground!\033[0m \n" for i in range(loop_times): # wait for all threads to finish
threads_list[i].join() print '\033[1;31;40m Main program waited until background was done at %s\033[0m \n' % ctime()

实例三输出结果如下:

多线程的数据共享与锁

前面已经提到过,多线程共享相同的运行环境,共享同一片数据空间。因此当多线程在操作同一数据时,有可能会出现数据同步不正确问题。例如有一个列表aList=[0,0,0,0,0,0,...]

如果有一个线程从后往前修改,将0改为1,同时又有另外一个进程从前往后读取aList。则有可能出现在读取过程中,前面一部分是0,后面一部分已经被篡改为了1.如下

数据不同步实例:

#!/usr/bin/env python
# -*- coding: utf-8 -*- import threading
from time import sleep aList = []
for i in range(100):
aList.append(0) class AsyncUpdate(threading.Thread):
def __init__(self):
super(AsyncUpdate, self).__init__() def run(self):
self.update_alist() def update_alist(self):
global aList
nums = len(aList) - 1
for i in range(nums, -1, -1):
aList[i] = 1
sleep(0.0001) class AsyncRead(threading.Thread):
def __init__(self):
super(AsyncRead, self).__init__() def run(self):
self.read_alist() def read_alist(self):
global aList
nums = len(aList)
for i in range(nums):
print aList[i], if __name__ == '__main__':
t1 = AsyncUpdate()
t2 = AsyncRead()
t1.start()
t2.start()
t1.join()
t2.join()

输入结果如下:

明显,以上实例证明了我们的观点,在多线程中,我们引出了线程同步的概念,锁。

假如我们在读取alist列表时,先锁定aList,不让其他线程更改,线程在更改aList时,锁定aList,不让其他线程操作,那么我们读取出来的aList就只能是全部为0或者全部为1了。如以下实例

数据同步实例:

#!/usr/bin/env python
# -*- coding: utf-8 -*- import threading
from time import sleep aList = []
for i in range(100):
aList.append(0) lock = threading.Lock() #创建线程锁 class AsyncUpdate(threading.Thread):
def __init__(self):
super(AsyncUpdate, self).__init__() def run(self):
# 先要获取锁:
lock.acquire()
try:
self.update_alist()
finally:
# 释放锁:
lock.release() def update_alist(self):
global aList
nums = len(aList) - 1
for i in range(nums, -1, -1):
aList[i] = 1
sleep(0.0001) class AsyncRead(threading.Thread):
def __init__(self):
super(AsyncRead, self).__init__() def run(self):
# 先要获取锁:
lock.acquire()
try:
self.read_alist()
finally:
# 释放锁:
lock.release() def read_alist(self):
global aList
nums = len(aList)
for i in range(nums):
print aList[i], if __name__ == '__main__':
t1 = AsyncUpdate()
t2 = AsyncRead()
t1.start()
t2.start()
t1.join()
t2.join()

输出结果如下:

当多个线程同时执行lock.acquire()时,只有一个线程能成功地获取锁,然后继续执行代码,其他线程就继续等待直到获得锁为止。

获得锁的线程用完后一定要释放锁,否则那些苦苦等待锁的线程将永远等待下去,成为死线程。所以我们用try...finally来确保锁一定会被释放。

死锁

锁的好处就是确保了某段关键代码只能由一个线程从头到尾完整地执行,坏处当然也很多,首先是阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了。其次,由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁,导致多个线程全部挂起,既不能执行,也无法结束,只能靠操作系统强制终止。

假定有两个资源 ResA,ResB。一个线程先锁定ResA,然后锁定ResB,一个线程先锁定ResB,然后再锁定ResA。在多线程运行时,假设线程1锁定了资源A,还没来得及锁定资源B,线程2就已经锁定了资源B,此时线程1等待线程2释放资源B,线程2等待线程1释放资源A,双方都在僵持着等待对方解锁才能继续执行,此时程序就成为死进程,死锁。如下实例:

#!/usr/bin/env python
# -*- coding: utf-8 -*- import threading
from time import sleep mutexA = threading.Lock()
mutexB = threading.Lock() class MyThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self) def run(self):
self.fun1()
self.fun2() def fun1(self):
""" fun1 先锁定资源A,然后再锁定资源B """
global mutexA, mutexB
if mutexA.acquire():
print "I am %s , locked res: %s" % (self.name, "ResA")
if mutexB.acquire():
print "I am %s , locked res: %s" % (self.name, "ResB")
# sleep(0.5)
mutexB.release()
print "I am %s , released res: %s" % (self.name, "ResB")
mutexA.release()
print "I am %s , released res: %s" % (self.name, "ResA") def fun2(self):
""" fun2 先锁定资源B,然后再锁定资源A """
global mutexA, mutexB
if mutexB.acquire():
print "I am %s , locked res: %s" % (self.name, "ResB")
sleep(0.5)
if mutexA.acquire():
print "I am %s , locked res: %s" % (self.name, "ResA")
mutexA.release()
print "I am %s , released res: %s" % (self.name, "ResA")
mutexB.release()
print "I am %s , released res: %s" % (self.name, "ResB") if __name__ == "__main__":
for i in range(0, 100):
my_thread = MyThread()
my_thread.start()

输出结果如下:

最后一行,显示Thread-2 锁定了ResA,同时,Thread-1 锁定了ResB,此时,Thread-2要等待ResB才能完成工作,Thread-1要等待ResA才能完成工作。各自一直处于等待中...

Queue实现生产者与消费者模型

#!/usr/bin/env python
# -*- coding: utf-8 -*- import threading, time
import Queue # 导入消息队列模块
import random # 导入随机数模块,是为了模拟生产者与消费者速度不一致的情形 q = Queue.Queue() # 实例化一个对象 def Producer(name): # 生产者函数
for i in range(10):
q.put(i) # 将结果放入消息队列中
print '\033[32;1m生产者 %s 生产了一个包子[%s],总包子数:%s\033[0m' % (name, i, q.qsize())
time.sleep(random.randrange(2)) # 生产者的生产速度,2s内 def Consumer(name): # 消费者函数
while True:
data = q.get() # 取用消息队列中存放的结果
print '\033[31;1m消费者 %s 消费了一个包子[%s],包子剩余:%s\033[0m' % (name, data, q.qsize())
time.sleep(random.randrange(1, 3)) # 消费者的消费速度,1-3s内 p = threading.Thread(target=Producer, args=('Milton',))
c = threading.Thread(target=Consumer, args=('Cherish',)) p.start()
c.start()

上例输出结果如下:

Queue是线程安全的,不需要加锁。如上截图箭头处“消费者 Cherish 消费了一个包子[4],包子剩余:4生产者 Milton 生产了一个包子[8],总包子数:4”

显然消费者 Cherish 消费了一个包子[4],包子剩余:4--》有朋友可能认为这里似乎包子剩余3才对,此处应该是出现了并发,在输出此语句前,生产者在队列中又生产了一个包子[8],所以吃完一个包子后,还是4,输出4是正确的。“生产者 Milton 生产了一个包子[8],总包子数:4” 此处生产包子[8]后,包子原本应该是5的,但是输出之前,又被Cherish消费了包子[4],所以输出4

同样由此处也可以看出,线程是安全的,在线程执行过程中,数据没有出错,包子没有多没有少,Milton总共生产了10个包子,Cherish总共吃了10个包子,最终剩下0个包子。

python 多线程学习的更多相关文章

  1. python多线程学习(一)

    python多线程.多进程 初探 原先刚学Java的时候,多线程也学了几天,后来一直没用到.然后接触python的多线程的时候,貌似看到一句"python多线程很鸡肋",于是乎直接 ...

  2. python多线程学习记录

    1.多线程的创建 import threading t = t.theading.Thread(target, args--) t.SetDeamon(True)//设置为守护进程 t.start() ...

  3. python 多线程学习小记

    python对于thread的管理中有两个函数:join和setDaemon setDaemon:如果在程序中将子线程设置为守护线程,则该子线程会在主线程结束时自动退出,设置方式为thread.set ...

  4. python多线程学习二

    本文希望达到的目标: 多线程同步原语:互斥锁 多线程队列queue 线程池threadpool 一.多线程同步原语:互斥锁 在多线程代码中,总有一些特定的函数或者代码块不应该被多个线程同时执行,通常包 ...

  5. Python多线程学习

    一.Python中的线程使用: Python中使用线程有两种方式:函数或者用类来包装线程对象. 1.  函数式:调用thread模块中的start_new_thread()函数来产生新线程.如下例: ...

  6. Python 多线程学习(转)

    转自:http://www.cnblogs.com/slider/archive/2012/06/20/2556256.html 引言 对于 Python 来说,并不缺少并发选项,其标准库中包括了对线 ...

  7. Python多线程学习资料1

    一.Python中的线程使用: Python中使用线程有两种方式:函数或者用类来包装线程对象. 1.  函数式:调用thread模块中的start_new_thread()函数来产生新线程.如下例: ...

  8. Python多线程学习笔记

    Python中与多线程相关的模块有 thread, threading 和 Queue等,thread 和threading模块允许程序员创建和管理线程.thread模块提供了基本的线程和锁的支持,而 ...

  9. 《转》Python多线程学习

    原地址:http://www.cnblogs.com/tqsummer/archive/2011/01/25/1944771.html 一.Python中的线程使用: Python中使用线程有两种方式 ...

随机推荐

  1. .Net Core 1.0.0正式版安装及示例教程

    使用VS Code 从零开始开发调试.NET Core 1.0 RTM. .NET Core 是一个开源的.跨平台的 .NET 实现. VS Code 全称是 Visual Studio Code,V ...

  2. eclipse安装ADT

    ADT安卓开发工具安装 ADT(Android Development Tools)安卓开发工具,是安卓在Eclipse IDE环境中的开发工具,为Android开发提供开发工具的升级或者变更,简单理 ...

  3. DIRECTORY_SEPARATOR:PHP 系统分隔符常量

    今天在nginx部署项目,在浏览器输入http://127.0.0.2/index.php/system/category/?action=list 老是提示error nginx配置没有问题,下了其 ...

  4. mybatis下报错:元素类型为 "mapper" 的内容必须匹配 "(cache-ref|cache|resultMap*|parameterMap

    今天使用别人的代码报错,但是有时又不报错原来是配置文件的顺序要遵守 注意 "必须匹配" 四个字, 其意味着顺序很重要, 必须要一致, 试试将 resultMap 中各元素的顺序修改 ...

  5. 【BZOJ-4281】Związek Harcerstwa Bajtockiego 树上倍增LCA

    4281: [ONTAK2015]Związek Harcerstwa Bajtockiego Time Limit: 10 Sec  Memory Limit: 256 MBSubmit: 167  ...

  6. Matlab中cell存储为txt

    clc clear all [data1,data3]=textread('E:\RSWeb\mahoyt数据集\movielens\u.user','%s%*d%s%*s%*s','delimite ...

  7. uva12716 gcd

    题意:给出N,1<=b<=a<=N,求满足gcd(a,b)=a xor b的pair (a,b)的个数 有个重要的结论:若gcd(a,b)=a xor b=c,那么b=a-c 如果一 ...

  8. MySQL备份方式简介

    MySQL备份的方式主要分为两种: 文本格式备份: 命令:mysqldump 转储文件:dump file 主要内容:数据库结构及数据(create talbe /insert) 二进制备份:这类备份 ...

  9. Oracle 应用于.NET平台

    1. 回顾ADO.NET ADO.NET是一组用于和数据源进行交互的面向对象类库集,它存在于.Net Framework中.通常情况下,数据源可以是各种类型的数据库,利用ADO.NET可以访问目前几乎 ...

  10. [Android]加密技术

    对称加密无论是加密还是解密都使用同一个key,而非对称加密需要两个key(public key和private key).使用public key对数据进行加密,必须使用private key对数据进 ...