4 个回答
如果是写操作,请用信号槽。不用信号槽的话,需要加锁,会影响性能。
子线程中对象emit信号。主线程中对象捕获后执行相关操作。
如果是单纯的读操作,很简单啊,你只要持有目标对象指针,目标对象写个type getXXX() const;方法,直接调用就ok了,和跨不跨线程没任何关系。
另外,请勿跨线程控制QWidget及其子对象,Qt不允许在非主线程控制GUI。
准确的说,QWidget, QTimer, QTjread, QIODevice对象,都有部分api不允许跨线程操作,否则会执行失败,控制台会有错误日志。
跨线程操作对象,这其实是个很朴素的所有权概念,只不过是逻辑意义上的所有权。
可以参照智能指针的内存所有权概念来类比,那个是实际意义上的所有权。
跨所有权的操作,只有一步到位的读操作是安全的,iterator这种多步执行的都可能遇到迭代器失效。而写操作是不安全的,必须通过加锁等代价很高的方式来进行。
因此,所有权是最重要的安全线越过了这条线,没人能保证你的安全,你只能付出极高的代价来实现,而且从逻辑层面上就是不合法的,严重破坏了封装性,提高了耦合度。
最优解,是退到安全线后,也就是用异步操作来实现所有写操作,如Qt的信号槽。只有在有极端实时需求时,才考虑直接越过安全线的暴力同步操作。
========================
========以下内容毁三观========
========================
然而,同步操作依旧保证不了实时性!
然而,同步操作依旧保证不了实时性!
然而,同步操作依旧保证不了实时性!
跨线程的同步写操作,必须加锁。加锁就会存在竞争,就会存在阻塞,该发生延迟的一样发生延迟。
如果你通过优化架构逻辑和代码算法,让两者尽可能不发生竞争了呢?
那异步同样没延迟了!
异步操作是如何实现的?
无非是把你的请求推送到对方线程的事件队列排队执行。这个也是跨线程写,只不过用了比如原子操作来实现了一个无锁队列。
当有竞争时,同步操作要阻塞等待,异步操作要队列等待,本质相同。
当没有竞争时,同步操作直接完成,异步操作入队后直接就出队了也是直接完成,没啥区别。
当然,同步操作里,发起方和执行方都在同一线程,不用担心对方会因线程调度无法实时响应。
然而发起方你自己也是线程,你自己也会被调度走的!在你加锁等待时,对方的线程也可能被调度走了导致你在空转的!
因此,只有在和系统,和硬件打交道时,由驱动层,由内核态发起中断或者回调,这时同步操作才有意义,因为可以保证发起者不会被调度走。
但依旧规避不了第二个问题——持有锁的目标线程被调度走后,你依旧只能在锁面前干等着。
所以,只要涉及到多线程,我不觉得同步操作的实时性有本质上的优势。
并且,只要不是做嵌入式,用户场合里99%代码都是全程在用户态执行。这时用同步操作,除了提高耦合,我不觉得有任何优势。
不然为毛js等语言纷纷要搞async/await,甚至连c++都在做提案了?
以前不是用回调写出了从操作系统到驱动程序到web应用的整个世界么?
原因还不是同步太违背工程学原理了么。
同步操作,归根结底是为了解决一种反人类的写法的——目标线程用while(true)处理任务,我要插队进去只能强行同步调用。
然而在异步编程里,即使是常驻操作也不会是while(true)。全都可以用事件系统进行事件响应。
比如网络服务端,不需要while检查请求,只需要监听socket的连接请求,接收请求就行了。
在处理请求时,同步模型里是加上锁的,无法响应同步调用。异步模型是线程在忙,调用事件排队等待。
在没处理请求时,同步模型可以直接拿到锁开始操作。异步模型下事件队列为空,来了新指令也能直接响应。
如果没外部请求,也没调用操作时,同步模型还得while(true)空转,异步模型线程直接停了,来事件时才会唤醒,岂不美哉?
所以,同步操作的那把锁,只不过是心理安慰,罢了。