06.系统编程-2.线程
1、多线程-threading
python的thread模块是比较底层的模块,python的threading模块是对thread做了一些包装的,可以更加方便的被使用
- 多线程执行
from threading import Thread
from time import sleepdef saySorry():print('亲爱的,我错了,我能吃饭了么?')sleep(1)if __name__ == '__main__':for i in range(5):t = Thread(target=saySorry)t.start() #启动线程,即让线程开始执行
- 主线程等待所有的子线程结束后才结束
from threading import Thread
from time import sleep,ctimedef sing():for i in range(3):print('正在唱歌……%s'%i)sleep(1)def dance():for i in range(3):print('正在跳舞……%s'%i)sleep(1)if __name__ == '__main__':print('开始了……%s'%ctime())t1 = Thread(target=sing)t2 = Thread(target=dance)t1.start()t2.start()sleep(5)print('结束了……%s' % ctime())运行结果:
开始了……Tue Jul 25 17:57:54 2017
正在唱歌……0
正在跳舞……0
正在唱歌……1
正在跳舞……1
正在跳舞……2
正在唱歌……2
结束了……Tue Jul 25 17:57:59 2017
2、线程类的封装
通过上一小节,能够看出,通过使用threading模块能完成多任务的程序开发,为了让每个线程的封装性更完美,所以使用threading模块时,往往会定义一个新的子类class,只要继承 threading.Thread 就可以了,然后重写 run 方法
from threading import Thread
from time import sleep#唱歌的类
class sing(Thread):def __init__(self,name):Thread.__init__(self)self.name = namedef run(self):for i in range(5):print("%s sing..."%self.name)sleep(1)class dance(Thread):def __init__(self, name):Thread.__init__(self)self.name = namedef run(self):for i in range(5):print("%s dance..." % self.name)sleep(1)if __name__ == '__main__':t1 = sing('gx')t2 = dance('gx')t1.start()t2.start()运行结果:
gx sing...
gx dance...
gx sing...
gx dance...
……
import os
import time
from threading import Thread,enumeratedef myRun():print('线程运行中...')time.sleep(3)print('线程结束...')class MyThread(Thread):def __init__(self,tname):Thread.__init__(self,name = tname)def run(self):self.myRun()def myRun(self):print('线程运行中...')time.sleep(3)print('线程结束...')if __name__ == '__main__':print('进程 %d.' % os.getpid())t1 = MyThread('老王的线程')t1.start()t1.join()print(t1.getName())print(enumerate())print('game over...')运行结果:
进程 11276.
线程运行中...
线程结束...
老王的线程
[<_MainThread(MainThread, started 10496)>]
game over...
说明:
python的threading.Thread类有一个run方法,用于定义线程的功能函数可以在自己的线程类中覆盖该方法。而创建自己的线程实例后,通过Thread类的start方法,可以启动该线程,交给python虚拟机进行调度,当该线程获得执行的机会时就会调用run方法执行线程。
总结
- 每个线程一定会有一个名字,尽管上面的例子中没有指定线程对象的name,但是python会自动为线程指定一个名字。
- 当线程的run()方法结束时该线程完成。
- 无法控制线程调度程序,但可以通过别的方式来影响线程调度的方式。
- 线程的几种状态
3、同步
同步就是协同步调,按预定的先后次序进行运行。如:你说完,我再说。
from threading import Thread
from time import sleepdef sing(name, ls):for item in ls:print("%s sing... %s"%(name,item))sleep(1)def kcup(name, ls):for item in ls:print("zm:%s:%s"%(name,item))sleep(1)if __name__ == '__main__':f = open('song.txt', 'r', encoding='utf-8')content = f.readlines()f.close()#print(content)t1 = Thread(target=sing,args=('lzy',content))t2 = Thread(target=kcup,args=('lzy',content))t1.start()t2.start()
4、多线程共享全局变量
4.1全局变量
from threading import Thread
import timeg_num = 100def work1():global g_numfor i in range(3):g_num += 1print("----in work1, g_num is %d---"%g_num)def work2():#global g_numprint("----in work2, g_num is %d---"%g_num)print("---线程创建之前g_num is %d---"%g_num)t1 = Thread(target=work1)
t1.start()#延时一会,保证t1线程中的事情做完
time.sleep(1)t2 = Thread(target=work2)
t2.start()运行结果:
---线程创建之前g_num is 100---
----in work1, g_num is 103---
----in work2, g_num is 103---
4.2列表作为实参
from threading import Thread
import timedef work1(nums):nums.append(44)print("----in work1---",nums)def work2(nums):#延时⼀会, 保证t1线程中的事情做完time.sleep(1)print("----in work2---",nums)g_nums = [11,22,33]
t1 = Thread(target=work1, args=(g_nums,))
t1.start()
t2 = Thread(target=work2, args=(g_nums,))
t2.start()运行结果:
----in work1--- [11, 22, 33, 44]
----in work2--- [11, 22, 33, 44]
总结:
- 在一个进程内的所有线程共享全局变量,能够完成多线程之间的数据共享(这点要比多进程要好)
- 缺点就是,线程是对全局变量随意修改可能造成多线程之间对全局变量的混乱( 即线程非安全)
4.3多线程操作全局变量——互斥锁
当多个线程一乎同时修改某一个共享数据的时候, 需要进行同步控制
线程同步能够保证多个线程安全访问竞争资源, 最简单的同步机制是引入互斥锁。
互斥锁为资源引入一个状态: 锁定/非锁定。
某个线程要更改共享数据时, 先将其锁定, 此时资源的状态为“锁定”, 其他线程不能更改; 直到该线程释放资源, 将资源的状态变成“非锁定”, 其他的线程才能再次锁定该资源。 互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。
threading模块中定义了Lock类, 可以方便的处理锁定:
创建锁
mutex = threading.Lock()
锁定
mutex.acquire([blocking])
释放
mutex.release()
其中, 锁定方法acquire可以有一个blocking参数。
如果设定blocking为True, 则当前线程会堵塞, 直到获取到这个锁为止
( 如果没有指定, 那么默认为True)
如果设定blocking为False, 则当前线程不会堵塞
使用互斥锁实现上面的例子的代码如下:
from threading import Thread,enumerate,Lock
import timeg_num = 0
# 创建一个互斥锁
# 这个锁默认是未上锁的状态
g_lock = Lock()def test1():global g_numfor i in range(1000000):#True表示堵塞 即如果这个锁在上锁之前已经被上锁了, 那么这个线程会在这里一直等#False表示非堵塞, 即不管本次调⽤能够成功上锁, 都不会卡在这,而是继续执行下面的isLock = g_lock.acquire()if isLock:g_num += 1g_lock.release()print("---test1---g_num=%d"%g_num) #这个在循环外!!!def test2():global g_numfor i in range(1000000):isLock = g_lock.acquire()if isLock:g_num += 1g_lock.release()print("---test2---g_num=%d"%g_num)p1 = Thread(target=test1)
p1.start()p2 = Thread(target=test2)
p2.start()print(enumerate())print("---g_num=%d---"%g_num)运行结果:
[<Thread(Thread-1, started 1632)>, <Thread(Thread-2, started 4808)>, <_MainThread(MainThread, started 7156)>]
---g_num=32952---
---test2---g_num=1757467
---test1---g_num=2000000
from threading import Thread,enumerate,Lock
import timeg_num = 0
g_lock = Lock()def test1():global g_numisLock = g_lock.acquire()if isLock:for i in range(1000000):g_num += 1print("---test1---g_num=%d" % g_num) #这句话放到循环中,最后运行结果就是想要的了g_lock.release()def test2():global g_numisLock = g_lock.acquire()if isLock:for i in range(1000000):g_num += 1print("---test2---g_num=%d" % g_num)g_lock.release()else:print('---test2---没有获取到锁,那我就走了')p1 = Thread(target=test1)
p1.start()p2 = Thread(target=test2)
p2.start()print(enumerate())
print("---g_num=%d---"%g_num)运行结果:
[<Thread(Thread-1, started 3760)>, <Thread(Thread-2, started 3780)>, <_MainThread(MainThread, started 10068)>]
---g_num=183906---
---test1---g_num=1000000
---test2---g_num=2000000
可以看到, 加入互斥锁后, 运行结果与预期相符。
上锁解锁过程
当一个线程调用锁的acquire()方法获得锁时, 锁就进入“locked”状态。
每次只有一个线程可以获得锁。 如果此时另一个线程试图获得这个锁, 该线程就会变为“blocked”状态, 称为“阻塞”, 直到拥有锁的线程调用锁的release()方法释放锁之后, 锁进入“unlocked”状态。
线程调度程序从处于同步阻塞状态的线程中选择一个来获得锁, 并使得该线程进入运行( running) 状态。
总结
锁的好处:
确保了某段关键代码只能由一个线程从头到尾完整地执行
锁的坏处:
阻止了多线程并发执行, 包含锁的某段代码实际上只能以单线程模式执行, 效率就大大地下降了
由于可以存在多个锁, 不同的线程持有不同的锁, 并试图获取对方持有的锁时, 可能会造成死锁
4.4死锁
在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁。
尽管死锁很少发生, 但一旦发生就会造成应用的停止响应。 下面看一个死锁的例子
from threading import Thread,Lock
from time import sleep#美国
class America(Thread):def __init__(self):Thread.__init__(self)def run(self):while True:if mutex1.acquire():print("军事演戏...")sleep(1)if mutex2.acquire():print('放弃军演')mutex1.release()#朝鲜
class NorthKorea(Thread):def __init__(self):Thread.__init__(self)def run(self):while True:flag = mutex2.acquire()if flag:print("核武开发")sleep(1)if mutex1.acquire():print('放弃核武开发')mutex2.release()if __name__ == '__main__':#lock:军事威胁 unlock:取消军事威胁mutex1 = Lock()#lock:开发核武 unlock:放弃核武mutex2 = Lock()#创建线程一t1 = America()#创建线程二t2 = NorthKorea()#启动线程t1.start()t2.start()运行代码:
军事演戏...
核武开发
避免死锁:
程序设计时要尽量避免( 银行家算法),
添加超时时间等
5、多线程-局部变量非共享数据
对于全局变量, 在多线程中要格外小心,否则容易造成数据错乱的情况发生
非全局变量是否要加锁呢?看下面一个例子
from threading import Thread
from time import sleepclass MyThread(Thread):def __init__(self,num):Thread.__init__(self)self.num = numdef run(self):self.num += 1sleep(1)print('num=%d'%(self.num))if __name__ == '__main__':t1 = MyThread(100)t1.start()t2 = MyThread(300)t2.start()运行结果:
num=301
num=101
总结
在多线程开发中, 全局变量是多个线程都共享的数据,而局部变量等是各自线程的,是非共享的
6、ThreadLocal
6.1 使用函数传参的方法
但是局部变量也有问题,就是在函数调用的时候,传递起来很麻烦:
def process_student(name):std = Student(name)# std是局部变量,但是每个函数都要用它,因此必须传进去:do_task_1(std)do_task_2(std)def do_task_1(std):do_subtask_1(std)do_subtask_2(std)def do_task_2(std):do_subtask_2(std)do_subtask_2(std)
每个函数一层一层调用都这么传参数那还得了? 用全局变量? 也不行,因为
每个线程处理不同的Student对象,不能共享。
6.2使用全局字典的方法
如果一个个全局dict存放所有的Student对象, 然后以thread自身作为key获得线程对应的Student对象如何?
from threading import Thread,current_threadclass Student(object):def __init__(self,name):self.name = namedef stu_process(name):stu = Student(name)print(current_thread())global_dict[current_thread()] = stustu_dotask1()stu_dotask2()def stu_dotask1():name = global_dict[current_thread()].nameprint('stu_dotask1:%s'%name)stu_dosubtask1()def stu_dotask2():name = global_dict[current_thread()].nameprint('stu_dotask2:%s'%name)stu_dosubtask2()def stu_dosubtask1():name = global_dict[current_thread()].nameprint('stu_dosubtask1:%s'%name)def stu_dosubtask2():name = global_dict[current_thread()].nameprint('stu_dosubtask2:%s' %name)global_dict = {}if __name__ == '__main__':t1 = Thread(target=stu_process,args=('xs1',))t1.start()t2 = Thread(target=stu_process,args=('xs2',))t2.start()
这种方式理论上是可行的,它最大的优点是消除了std对象在每层函数中的传递问题,但是,每个函数获取std的代码有点low。
6.3使用ThreadLocal的方法
ThreadLocal应运而生,不用查找dict,ThreadLocal帮你自动做这件事:(可以看做是包装了dict的方法)
from threading import Thread,local,current_threadclass Student(object):def __init__(self,name):self.name = namedef stu_process(name):#在local_school添加一个student属性local_shool.student = name#stu_dotask1()stu_dotask2()def stu_dotask1():name = local_shool.studentprint('stu_dotask1:%s'%name)stu_dosubtask1()def stu_dotask2():name = local_shool.studentprint('stu_dotask2:%s'%name)stu_dosubtask2()def stu_dosubtask1():name = local_shool.studentprint('stu_dosubtask1:%s'%name)def stu_dosubtask2():name = local_shool.studentprint('stu_dosubtask2:%s' %name)#定义Threadlocal对象
local_shool = local()if __name__ == '__main__':t1 = Thread(target=stu_process,args=('xs1',))t1.start()t2 = Thread(target=stu_process,args=('xs2',))t2.start()运行结果:
stu_dotask2:xs1
stu_dosubtask2:xs1
stu_dotask2:xs2
stu_dosubtask2:xs2
说明
全局变量local_school就是一个ThreadLocal对象,每个Thread对它都可以读写student属性,但互不影响。 你可以把local_school看成全局变量,但每个属性如local_school.student都是线程的局部变量,可以任意读写而互不干扰,也不用管理锁的问题,ThreadLocal内部会处理。
可以理解为全局变量local_school是一个dict,不但可以用local_school.student,还可以绑定其他变量,如local_school.teacher等等。
ThreadLocal最常用的地方就是为每个线程绑定一个数据库连接,HTTP请求,用户身份信息等,这样一个线程的所有调用到的处理函数都可以非常方便地访问这些资源。
6.4总结
一个ThreadLocal变量虽然是全局变量,但每个线程都只能读写自己线程的独立副本,互不干扰。ThreadLocal解决了参数在一个线程中各个函数之间互相传递的问题
7、异步
同步调用就是你 喊 你朋友吃饭 ,你朋友在忙 ,你就一直在那等,等你朋友忙完了 ,你们一起去
异步调用就是你 喊 你朋友吃饭 ,你朋友说知道了 ,待会忙完去找你 ,你就去做别的了。
from multiprocessing import Pool
import time
import osdef test():print("---进程池中的进程---pid=%d,ppid=%d--"%(os.getpid(),os.getppid())for i in range(3):print("----%d---"%i)time.sleep(1)return "hahah"def test2(args):print("---callback func--pid=%d"%os.getpid())print("---callback func--args=%s"%args)pool = Pool(3)
pool.apply_async(func=test,callback=test2)
time.sleep(5)
print("----主进程-pid=%d----"%os.getpid())运行结果:
---进程池中的进程---pid=9401,ppid=9400--
----0---
----1---
----2---
---callback func--pid=9400
---callback func--args=hahah
----主进程-pid=9400----
作业题
模拟多线程下载,10个线程,每个线程下载10个数据,(线程1:10,12...19,线程2:20,21...29,类推),最后合成存入一个文件
from threading import Thread
from time import sleepls = ['','','','','','','','','','']#下载的线程类
class Down(Thread):def __init__(self, index):Thread.__init__(self)self.index = indexdef run(self):result = ""for i in range(10):s = str(self.index) + str(i)print(s)result = result + ssleep(0.5)print(result)ls[self.index] = resultif __name__ == '__main__':for i in range(10):t = Down(i)t.start()sleep(8)print(ls)#保存f = open('data.txt', 'w', encoding='utf-8')#join:列表元素链接成一个字符串,s = ':'.join(ls)print(s)f.write(s)f.close()
转载于:https://www.cnblogs.com/cjr0707/p/9695466.html
06.系统编程-2.线程相关推荐
- linux系统编程:线程原语
线程原语 线程概念 线程(thread),有时被称为轻量级进程(Lightweight Process,LWP).是程序运行流的最小单元.一个标准的线程由线程ID.当前指令指针(PC),寄存器集合和堆 ...
- 06.系统编程-3.进程VS线程比较
1.定义的不同 ==进程是系统进行资源分配和调度的一个独立单位.== ==线程是进程的一个实体,是CPU调度和分派的基本单位==,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只 ...
- 【Linux系统编程】线程池
00. 目录 文章目录 00. 目录 01. 线程池原理 02. 线程池应用实例 03. 线程池代码 04. 附录 01. 线程池原理 在传统服务器结构中,常是有一个总的监听线程监听有没有新的用户连接 ...
- 【Linux系统编程】线程私有数据
00. 目录 文章目录 00. 目录 01. 线程之间共享数据 02. 线程私有数据 2.1 创建线程私有数据 2.2 销毁线程私有数据 2.3 关联线程私有数据成员 2.4 读取线程私有数据所关联的 ...
- 【Linux系统编程】线程栈大小
00. 目录 文章目录 00. 目录 01. 概述 02. 设置线程栈函数 03. 附录 01. 概述 先来讲说线程内存相关的东西,主要有下面几条: 进程中的所有的线程共享相同的地址空间. 任何声明为 ...
- 【Linux系统编程】线程基本操作
00. 目录 文章目录 00. 目录 01. 线程概述 02. 线程常用函数 2.1 获取线程号 2.2 线程号比较 2.3 线程创建 2.4 回收线程资源 2.5 线程分离 2.6 线程退出 03. ...
- 【Linux系统编程】线程的基本操作
概述 每个进程都拥有自己的数据段.代码段和堆栈段,这就造成进程在进行创建.切换.撤销操作时,需要较大的系统开销.为了减少系统开销,从进程中演化出了线程.为了让进程完成一定的工作,进程必须至少包含一个线 ...
- 【Linux系统编程】线程同步与互斥:POSIX无名信号量
信号量概述 信号量广泛用于进程或线程间的同步和互斥,信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问. 编程时可根据操作信号量值的结果判断是否对公共资源具有访问的权限,当信号量值大于 ...
- 【Linux系统编程】线程堆栈大小的使用介绍
先来讲说线程内存相关的东西,主要有下面几条: 进程中的所有的线程共享相同的地址空间. 任何声明为 static/extern 的变量或者堆变量可以被进程内所有的线程读写. 一个线程真正拥有的唯一私有储 ...
最新文章
- 锻炼能降低13种癌症风险
- structs2文件下载
- javascript基础:函数参数与闭包问题
- bupt summer training for 16 #3 ——构造
- 风控小白入门 | 关于评分模型验证的7大问题回答
- JAVA如何选中一行上移_js操作table中tr的顺序实现上移下移一行的效果
- ENVI扩展工具——混合像元分解FCLS
- 摄影测量学——解析法相对定向
- Layui table隐藏某一列
- eXeScope之类的程序资源修改器的使用,很牛!
- 单项选择题标准化考试系统设计c语言报告,C语言课程设计(单项选择题标准化考试系统)报告...
- 【蓝牙串口无线烧写程序】适用于STM32F103和STM32F107的Bootloader
- (PKCS1) RSA 公私钥 pem 文件解析
- SQL Server安装和修改身份验证方式
- 解决TortoiseSVN不显示状态图标(图文步骤详解)
- 行为型模式:备忘录模式
- Mac 安装 Icarus-Verilog 报错解决方案参考
- Flask教程(二十)flask-apscheduler
- 为上次渲染的三角形添加颜色
- html锚点链接怎么互点,通过优化网页核心关键词快速提升排名
热门文章
- spss相关性分析看结果_利用spss做Pearson相关性分析步骤详解
- python2 队列的使用_使用2个队列创建堆栈
- python 多线程ping测试_从线程ping多个ip时Python ICMP ping实现?
- 分峰截幅c语言算法,面向桥梁健康监测的复合传感技术研究
- 从三个角度判断网站优劣程度值得了解一下
- 内链优化对于网站有哪些作用?
- 网站排名不稳定要从多方面进行分析
- java script 遍历数组_JavaScript中数组中遍历的方法
- python条形图数据标签_python – Plotly中用于条形图的单独标记条形图
- 查看suse系统版本