线程真不是好玩的

作者长期对线程谨慎,此次用QtE做UI相关库,因程序在非UI线程调用,需将UI操作转移到UI线程。单元测试顺利,但调用函数时偶尔出现Segmentation Fault,经测试发现程序运行261次循环被Killed,怀疑是pthread_create未pthread_join导致资源未释放,后修改程序设置pthread属性不用join。
部署运行你感兴趣的模型镜像

早就耳闻线程是一个可怕的东西,弄不好会让人疯掉的,所以长期以来,我一向对线程相当谨慎,甚至有点敬而远之,我相信
1) 决不设计过于复杂的多线程代码;
2) 必须依靠多线程实现的逻辑,一定要能够相当简单。
如果多线程逻辑复杂了,到时候出了错都不知道什么地方错了,而且错误无法复现,这是最要命的。

最近需要用QtE做一个UI相关的库,问题是,使用这个UI库的程序是在非UI线程上调用这个库的,而QT的创作者Trolltech的技术人员谆谆教诲 我们不要在非UI线程上做UI操作。说道这个要求,倒不是只有QT这么干的。因为UI操作最终要作用于存储显示内容的内存资源,如果容许多个线程能够操作 这个资源,毫无疑问要想不出岔子,就需要用上线程同步,线程同步的代码谁来写呢?如果UI库自己来作同步,那么效率就很成问题,如果让使用UI库的 programmer来做同步,那么programmer又会很难受,所以几乎所有的UI库干脆要求只有一个线程能够做UI操作,这个线程就是UI线程, 其工作就是和一个抽水泵一样不停的接受事件然后分配给特定的widget。但是在QT上面,如果违反这个原则,在非UI线程上做UI操作,很有可能不会发 生任何异常,但在某个时候,问题又会产生,“偷走你的银行存款,偷走你的女朋友“(Trolltech开发人员如此描述可能发生的问题),所以,要想保证 质量,必须保证所有的UI操作都在UI线程上面进行。

回到我的工作上来, 为了达到能够在非UI线程(在QT的应用程序里,除了UI进程,其他的进程都叫“非UI线程“),我不得不用上postEvent,用这种方式,在非UI 线程上抛出一个event,这个event会进入进程的event loop,然后被UI线程的抽水泵抓住,这样,非UI线程就通过这种方式把UI操作转移到UI线程上。

一切都进行得很好,单元测试也很顺利,但是使用我的库的程序员报告说调用一个函数的时候偶尔会出现Segmentation Fault,因为不是总发生这种错误,第一反应就是和线程相关的逻辑有问题。为了复现这个错误,我编了一个只使用这个库里一个功能的程序,为了模拟在非 UI线程中调用这个函数,我在一个死循环中通过pthread_create创建线程,在线程里面创建并显示一个widget,然后隐藏并释放它。这也算 是一个压力测试。结果程序运行到261次循环的时候就被Killed了,这和报告的Segementation Fault不一样,Killed一般意味着内存不足了,我仔细看了一下库的代码,逻辑很简单,new过的东西都被delete了,应该不会产生内存泄露, 我有运行了一遍这个测试程序,还是被Killed了,而且还是杂261次循环的时候,我就心存疑问了,为什么总是在261次的时候,肯定是什么资源没有被 释放,261次之后就消耗光了。当天没有找出什么端倪来,在回家的路上想到,pthread_create一般是需要被pthread_join的,会不 会是因为没有pthread_join,所有有资源没有释放呢。第二天把程序修改了一下,不用pthread_join,可以把pthread的属性设成 不用join
  pthread_attr_t attr;
  pthread_attr_init(&attr);
  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
  pthread_t tid;
  pthread_create(&tid, &attr, &thread_show, this);
  pthread_attr_destroy(&attr);

这样killed的问题没有,但是,Segementation Fault的问题出现了,经过反复运行测试程序,发现Fault出现的地方总是在delete一个widget之后,原来,我原来的代码中虽然创建和显示 widget的code保证是在UI线程上执行的,但是delete这个widget却很马虎的是从非UI线程上执行的。把这个修改过来之后, Segementation Fault就再没发生过。

这个bug给了这样的教训
1) pthread_create产生的线程占用资源,通过pthread_join回收资源,或者设成detached thread,这样就不需要pthread_join了,线程结束运行自动释放资源;
2) 对UI的擦操作必须在UI线程上,不仅指的是widget的创建和显示,还包括widget的删除。

您可能感兴趣的与本文相关的镜像

Stable-Diffusion-3.5

Stable-Diffusion-3.5

图片生成
Stable-Diffusion

Stable Diffusion 3.5 (SD 3.5) 是由 Stability AI 推出的新一代文本到图像生成模型,相比 3.0 版本,它提升了图像质量、运行速度和硬件效率

### 推荐的有趣 Python 项目 #### 基于图片处理的创意项目 通过图像转换技术,可以实现将一张彩色照片转化为字符画的艺术效果。这种项目不仅能够帮助理解像素的概念以及颜色模型的应用,还能提升对文件读写操作的理解能力[^1]。 ```python from PIL import Image def image_to_ascii(image_path, output_width=100): img = Image.open(image_path) width, height = img.size aspect_ratio = height / float(width * 2) new_height = int(output_width * aspect_ratio) resized_image = img.resize((output_width, new_height)) grayscale_chars = "@%#*+=-:. " pixels = list(resized_image.convert('L').getdata()) ascii_str = ''.join([grayscale_chars[pixel//32] for pixel in pixels]) lines = [''.join(ascii_str[i:i+output_width]) for i in range(0, len(ascii_str), output_width)] return "\n".join(lines) print(image_to_ascii("example.jpg")) ``` --- #### 构建聊天应用程序 创建一个支持多人在线交流的基础版聊天室是一个非常实用的学习案例。此过程会涉及到网络通信协议(TCP/IP)、图形界面设计(Tkinter),还有多线程管理等内容[^3]。 ```python import socket import threading server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) host = 'localhost' port = 9999 server_socket.bind((host, port)) server_socket.listen() clients = [] def broadcast(message): for client in clients: client.send(message) def handle_client(client_socket): while True: try: message = client_socket.recv(1024).decode() if not message: break broadcast(f"{message}".encode()) except Exception as e: print(e) break index = clients.index(client_socket) del clients[index] client_socket.close() while True: client_socket, address = server_socket.accept() clients.append(client_socket) thread = threading.Thread(target=handle_client, args=(client_socket,)) thread.start() ``` --- #### 游戏开发入门——贪吃蛇小游戏 利用 Pygame 库制作经典游戏《贪吃蛇》,可以让初学者熟悉事件循环机制、碰撞检测算法以及动态更新屏幕画面的技术要点[^2]。 ```python import pygame pygame.init() screen = pygame.display.set_mode((800, 600)) snake_pos = [[100, 50], [90, 50], [80, 50]] direction = 'RIGHT' running = True while running: screen.fill((0, 0, 0)) for pos in snake_pos: pygame.draw.rect(screen,(255,255,255),(pos[0],pos[1],10,10)) for event in pygame.event.get(): if event.type == pygame.KEYDOWN: if event.key == pygame.K_UP and direction != 'DOWN': direction = 'UP' elif event.key == pygame.K_DOWN and direction != 'UP': direction = 'DOWN' elif event.key == pygame.K_LEFT and direction != 'RIGHT': direction = 'LEFT' elif event.key == pygame.K_RIGHT and direction != 'LEFT': direction = 'RIGHT' if direction == 'UP': snake_pos.insert(0,[snake_pos[0][0], snake_pos[0][1]-10]) elif direction == 'DOWN': snake_pos.insert(0,[snake_pos[0][0], snake_pos[0][1]+10]) elif direction == 'LEFT': snake_pos.insert(0,[snake_pos[0][0]-10, snake_pos[0][1]]) elif direction == 'RIGHT': snake_pos.insert(0,[snake_pos[0][0]+10, snake_pos[0][1]]) snake_pos.pop() pygame.display.flip() pygame.time.Clock().tick(10) pygame.quit() ``` ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值