TKinter GUI库没有像MFC那样的强制安全策略,在MFC中子线程无法直接控制主线程中的GUI控件,比如修改文本框或者按钮上的内容。因为如果只有2个线程,即一个主线程和一个子线程的情况下子线程直接修改主线程控件的相关变量是没什么问题的,也不会出错,但是如果子线程个数大于1,就可能会出现多个线程同时修改一个控件相关变量的情况,导致混乱出错。所以MFC中的办法是自定义一个消息,然后子线程调用这个自定义的消息给主线程发送消息,再由主线程选择响应或者不响应,这样选择权就在主线程,因而可以避免出错。但是Python的TKinter库没这限制,子线程也可以直接访问主线程控件相关变量,也就是说需要用户自己去实现MFC相似的功能,这里就需要用到Python为线程设计的Queue模块,也就是队列。

这个模块可以模拟栈那样先进先出的模式也可以像韩信管谷仓那样搞推陈出新,可以想象成一根管子,把球往管子里塞,最后球从这边口进去会从那边的口出来。用这种方式来接收子线程发送过来的消息,然后在取出消息时判断是哪个线程发送过来的,再把之分配给相应的控件即可。

Queue队列:

下面是例子:

import tkinter as tk
from time import sleep
import queue as Queue
import threadingclass GUI():iolaw = None#申明变量的类型  phelda = None#申明变量的类型msg_queue = None#创建一个队列def __init__(self, root):self.iolaw = tk.StringVar()#申明变量的类型  self.phelda = tk.StringVar()#申明变量的类型self.msg_queue = Queue.Queue()#创建一个队列self.initGUI(root)#循环读取队列中的内容刷新控件中的内容def handee(self,root):#把队列中的内容取出赋值给label控件mpica=self.msg_queue.empty()#检查队列是否为空if(mpica==False):ontad=self.msg_queue.get()ourta=ontad.split(",")if(ourta[0]=="1"):self.iolaw.set(ourta[1])else:if(ourta[0]=="2"):self.phelda.set(ourta[1])root.after(500, self.handee,root)#递归调用实现循环,TKinter UI线程中无法使用传统的while循环只能用它这个自带的函数递归实现循环def hit_me(self):#点击按钮启动两个子线程thread = threading.Thread(target=self.line01)wimme = threading.Thread(target=self.line02)thread.start()wimme.start()def line01(self):#线程回调函数for i in range(1,10) :mmer=str(i)summes="1,"+mmerself.msg_queue.put(summes)sleep(1)self.msg_queue.put("1,发送完毕")def line02(self):#线程回调函数for i in range(20,30) :mmer=str(i)oceanw="2,"+mmerself.msg_queue.put(oceanw)sleep(1)self.msg_queue.put("2,发送完毕")def initGUI(self, root):root.geometry('500x300')  # 这里的乘是小xfm1 = tk.Frame(root)tk.Button(fm1, text="按钮",command=self.hit_me).grid(row=0,column=0,pady=10,sticky=tk.N,columnspan=2)tk.Label(fm1, textvariable=self.iolaw,bg='green',fg='white',width=20, height=3).grid(row=1,column=0)tk.Label(fm1, textvariable=self.phelda,bg='red',fg='white',width=20, height=3).grid(row=1,column=1)fm1.pack()root.after(100, self.handee,root)root.mainloop()if __name__ == "__main__":root = tk.Tk()myGUI = GUI(root)

例子中用两个子线程向Queue压入消息,在主线程中取出消息,切割字符串后判断是哪一线程发过来的消息,判断后分发给不同的控件。

多进程版本

大家都知道Python的多线程其实是伪多线程,因为Python代码的执行由Python虚拟机(解释器)来控制。Python在设计之初就考虑要在主循环中,同时只有一个线程在执行,就像单CPU的系统中运行多个进程那样,内存中可以存放多个程序,但任意时刻,只有一个程序在CPU中运行。同样地,虽然Python解释器可以运行多个线程,只有一个线程在解释器中运行。

对Python虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同时只有一个线程在运行。在多线程环境中,Python虚拟机按照以下方式执行。

1.设置GIL。

2.切换到一个线程去执行。

3.运行。

4.把线程设置为睡眠状态。

5.解锁GIL。

6.再次重复以上步骤。

对所有面向I/O的(会调用内建的操作系统C代码的)程序来说,GIL会在这个I/O调用之前被释放,以允许其他线程在这个线程等待I/O的时候运行。如果某线程并未使用很多I/O操作,它会在自己的时间片内一直占用处理器和GIL。也就是说,I/O密集型的Python程序比计算密集型的Python程序更能充分利用多线程的好处。

我们都知道,比方我有一个4核的CPU,那么这样一来,在单位时间内每个核只能跑一个线程,然后时间片轮转切换。但是Python不一样,它不管你有几个核,单位时间多个核只能跑一个线程,然后时间片轮转。看起来很不可思议?但是这就是GIL搞的鬼。任何Python线程执行前,必须先获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都给上了锁,所以,多线程在Python中只能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核。通常我们用的解释器是官方实现的CPython,要真正利用多核,除非重写一个不带GIL的解释器。

所以如果要实现真·多线程在Python里用的是多进程的方式,不然是无法真正利用多核CPU的并发处理能力的,但是进程和进程之间是隔离的,进程之间的通信就不像多线程那样方便,下面是例子:

from time import sleep
from multiprocessing import Process,Queue
import tkinter as tkclass Gui():root = tk.Tk()iolaw = tk.StringVar()#申明变量的类型  phelda = tk.StringVar()#申明变量的类型ngit= Queue()def __init__(self):self.nteres(self.root,self.ngit,self.iolaw,self.phelda)#创建窗口主程序逻辑def line01(self,q):#线程回调函数for i in range(1,10) :mmer=str(i)summes="1,"+mmerq.put(summes)sleep(1)q.put("1,发送完毕")def line02(self,q):#线程回调函数for i in range(20,30) :mmer=str(i)oceanw="2,"+mmerq.put(oceanw)sleep(1)q.put("2,发送完毕")def confid(self,q):p1=Process(target=self.line01,args=(q,)) #创建子进程p2=Process(target=self.line02,args=(q,))p1.start()p2.start()def itudew(self,root,q,iolaw,phelda):#读出队列中的信息mpica=q.empty()#检查队列是否为空if(mpica==False):ontad=q.get()ourta=ontad.split(",")if(ourta[0]=="1"):iolaw.set(ourta[1])else:if(ourta[0]=="2"):phelda.set(ourta[1])root.after(100,self.itudew,root,q,iolaw,phelda)def nteres(self,root,q,iolaw,phelda):root.geometry('500x300')  # 这里的乘是小xfm1 = tk.Frame(root)tk.Button(fm1, text="按钮",command=lambda: self.confid(q)).grid(row=0,column=0,pady=10,sticky=tk.N,columnspan=2)tk.Label(fm1, textvariable=iolaw,bg='green',fg='white',width=20, height=3).grid(row=1,column=0)tk.Label(fm1, textvariable=phelda,bg='red',fg='white',width=20, height=3).grid(row=1,column=1)fm1.pack()root.after(100, self.itudew,root,q,iolaw,phelda)root.mainloop()if __name__ == "__main__":amerie=Gui()

多进程之间交换数据有好几种办法,在这里鄙人依旧使用Queue容器,只不过这里的Queue不是tk对象下的Queue,而是多进程模块multiprocessing下的Queue。在多进程方案下关于Tk的对象或者说变量不能直接用self.××的方式直接引用,而只能先在类中声明类成员变量(属性),再把变量通过函数参数的方式传入函数内部使用,直接self.××(变量名)的方式引用会报错,但如果是多进程方案择不会。或者也可以在  if __name__ == "__main__": 这一句后声明变量再通过创建类实例的时候通过构造函数传入。

Pipe通道:

这个pipe不同于对文件进行IO操作的那个os.Pipe,此Pipe是multiprocessing跨进程通信模块下的同名Pipe类。

Pipe常用于两个进程,两个进程分别位于管道的两端

Pipe方法返回(conn1,conn2)代表一个管道的两个端,Pipe方法有duplex参数,默认为True,即全双工模式,若为FALSE,conn1只负责接收信息,conn2负责发送,

send和recv方法分别为发送和接收信息。

例:

#!coding:utf-8
import multiprocessing
import os,time,random#写数据进程执行的代码
def proc_send(pipe,urls):#发送函数for url in urls:print('Process is send :%s' %url)pipe.send(url)time.sleep(random.random())#读数据进程的代码
def proc_recv(pipe):while True:print('Process rev:%s' %pipe.recv())time.sleep(random.random())if __name__ == '__main__':#父进程创建pipe,并传给各个子进程haha,lala = multiprocessing.Pipe()p1 = multiprocessing.Process(target=proc_send,args=(lala,['url_'+str(i) for i in range(10) ]))p2 = multiprocessing.Process(target=proc_recv,args=(haha,))#启动子进程,写入p1.start()p2.start()p1.join()p2.terminate()

如果只给Pipe一个接收变量这个变量会自动变成一个列表,例如:

    #父进程创建pipe,并传给各个子进程pipe = multiprocessing.Pipe()p1 = multiprocessing.Process(target=proc_send,args=(pipe[0],['url_'+str(i) for i in range(10) ]))p2 = multiprocessing.Process(target=proc_recv,args=(pipe[1],))

参考资料:

TKinter在多线程时刷新GUI的一些碎碎念

Python GUI之tkinter窗口视窗教程大集合(看这篇就够了)

Python 队列(Queue)用法

python进程间通信

Python进阶:聊聊IO密集型任务、计算密集型任务,以及多线程、多进程

Python GUI库TKinter子线程与主线程控件传递消息策略相关推荐

  1. c#子线程调用主线程控件

    相信对多线程有所了解的人都知道,子线程是不能直接操作winform上的控件的,因为默认的控件是在主线程上生成的,子线程是不能直接访问或者修改的,直接访问或者修改控件属性的话会报错.这个即使在Java上 ...

  2. 模态对话框阻塞主线程的话不影响其他线程操作主线程控件(不阻塞)

    Task.Factory.StartNew(() => {Thread.Sleep(5000);this.Invoke(new Action(() => {this.button7.Tex ...

  3. Qt自定义事件实现及子线程向主线程传送事件消息

    近期在又一次学习Qt的时候,由于要涉及到子线程与主线程传递消息,所以便琢磨了一下.顺便把有用的记录下来,方便自己以后查询及各位同仁的參考! 特此声明,本篇博文主要讲述有用的,也就是直接说明怎么实现,就 ...

  4. Python GUI库 Tkinter入门资料 -- 高级应用

    3. 高级用法 通过基础篇的学习,相信大家已经掌握了简单的tkinter编程,但如果想做出真正实用的程序,还需要学习一些高级用法,一些更复杂的控件. 3.1 高级控件学习 控件类 名称 简要说明 La ...

  5. Python GUI库Tkinter的使用

    一,公共知识 1.第一个小程序(参考) from tkinter import * from tkinter import messageboxdef songhua(e): # e为事件对象mess ...

  6. Python 多个线程按先后顺序执行,并保持各子线程和主线程的通信

    Python 多个线程按先后顺序执行,并保持各子线程和主线程的通信 摘要 最近有个项目使用pyqt5写的界面,界面展示部分作为项目的主线程,另外通过调用Thread,传入不同的参数又设置了五个子线程, ...

  7. python的gui库哪个好_常用的13 个Python开发者必备的Python GUI库

    [Python](http://www.blog2019.net/tag/Python?tagId=4)是一种高级编程语言,它用于通用编程,由Guido van Rossum 在1991年首次发布.P ...

  8. python自带gui_一个极简易上手的 Python GUI 库

    原标题:一个极简易上手的 Python GUI 库 很多同学学了 Python 之后都想开发带界面的程序,也就是 GUI 应用.一般用的比较多的 GUI 库是 Tkinter(Python 自带)和 ...

  9. python图形界面库哪个好_8个必备的Python GUI库

    Python GUI 库有很多,下面给大家罗列常用的几种 GUI库.下面介绍的这些GUI框架,能满足大部分开发人员的需要,你可以根据自己的需求,选择合适的GUI库. 1. wxPython wxPyt ...

最新文章

  1. C++ 一个例子彻底搞清楚拷贝构造函数和赋值运算符重载的区别
  2. delete mysql 大表_无语了,直到今天,我才揪出MySQL磁盘消耗迅猛的“真凶”!
  3. HP Z240组建磁盘阵列RAID1
  4. ASP.NET Core改进了.NET Framework中的字符串处理
  5. ngrok服务器搭建_利用暴露在外的API,无法检测的Linux恶意软件将矛头指向Docker服务器...
  6. 我国快递年业务量首次突破千亿件大关
  7. C++子类的构造函数
  8. mac 恢复未能与服务_苹果电脑恢复macOS系统,磁盘被锁或无法识别到磁盘怎么回事呢?...
  9. 多屏互动之Duet Display和Air Display
  10. js锅打灰太狼小游戏
  11. mysql +cobar_MySQL 中间件 cobar 初体验
  12. 求出字符串中大写字母,小写字母和数字的个数
  13. 祝你元宵节快乐!今朝逢元夜,花与灯依旧。众里寻他千百度。蓦然回首,那人却在,灯火阑珊处。...
  14. Android浏览器翻译功能调研
  15. \t\tP2P终结者原理
  16. 如何计算用户生命周期天数?
  17. c# picturebox 刷新_C# picturebox画图问题
  18. linux打开网络摄像头失败,Opencv没有检测到linux上的firewire网络摄像头
  19. 分享微信点餐小程序搭建步骤_微信点餐功能怎么做
  20. QQ气泡聊天核心代码与QQ列表问题

热门文章

  1. H5 hbuilder打包后 plusready监听不到
  2. leyou商城day2 商品分类
  3. opencv imshow更改窗口大小
  4. mingw-w64 全网最全
  5. linux的常用软件
  6. itouch 4越狱
  7. 远程管理特洛伊木马(RAT)病毒
  8. python显示html内容自动换行_canvas绘制文本内容自动换行
  9. oracle ora 04080,请问我的帖子为何不能回复呢
  10. 从支付宝、微信到有道翻译官,中国二维码频频风靡海外