Qt - 一文理解QThread多线程(万字剖析整理)

article/2025/10/16 9:12:34

在这里插入图片描述

目录

  • 为什么需要多线程
  • QThread使用方法
    • new QThread Class & Override run()
    • new Object Class & moveToThread(new QThread)
  • connect
  • 事件循环
  • 源码分析
  • 如何正确退出线程
  • 堆栈大小
  • 优先级
  • 线程间通讯
  • 线程同步
    • 互斥锁
    • 读写锁
    • 信号量
    • 条件变量
  • 可重入与线程安全
  • QObject的可重入性
  • 开启多少个线程合理
  • 参考鸣谢

为什么需要多线程

在开发存在界面交互的程序中,为了使一些耗时操作不造成卡顿;我们一般会将这些耗时操作放到子线程中进行处理,常见的如一些同步通讯。

虽然已编写过几次多线程的程序,但是每次使用都感觉心里不踏实,借用 QThread 总结一下罢。

QThread使用方法

Qt 中使用多线程,必然绕不开的是 QThread。建议先过一遍 QThread Class 文档。

文档中演示了两种使用方法:

new QThread Class & Override run()

/*------------------------------WorkerThread-----------------------------------*/
class WorkerThread : public QThread
{Q_OBJECT
public:explicit WorkerThread();
protected:void run();
signals:void resultReady(const QString &s);
};void WorkerThread::run(){/* ... here is the expensive or blocking operation ... */
}/*------------------------------MainWindow-----------------------------------*/
void MainWindow::startWorkInAThread()
{WorkerThread *workerThread = new WorkerThread();// Release object in workerThreadconnect(workerThread, &WorkerThread::finished, workerThread, &QObject::deleteLater);workerThread->start();
}

需要 注意 的:

  • run() 中未调用 exec() 开启 even loop,那么在 run() 执行结束时,线程将自动退出。

  • 该案例中,WorkerThread 存在于实例化它的旧线程中,仅有 run() 中是在子线程中执行的。我们可以通过以下代码 打印线程ID 进行验证:

    qDebug()<<"mythread QThread::currentThreadId()==" << QThread::currentThreadId();
    

这就存在一个尴尬的问题,如果在 WorkerThreadrun() 中使用了 WorkerThread 的成员变量,而且 QThread的其他方法也使用到了它,即我们从不同线程访问该成员变量,这时需要自行检查这样是否安全。

在这里插入图片描述
这个例子也说明了,QThread 实例本身并不是一个线程,正如 QThread Class 开篇点明这是一个 线程管理类

The QThread class provides a platform-independent way to manage threads.

注意:这种使用方法并不推荐,至于它为什么仍然出现在 QThread的文档里作为案例 “误导” 我们,这貌似是个历史问题。

Qt 4.4 版本以前的 QThread 类是个抽象类,要想编写多线程代码唯一的做法就是 继承 QThread 类。该论断在 Qt4.3QThread Class 中可以印证。

但是之后的版本中,Qt 库完善了线程的亲和性以及信号槽机制,我们有了更为优雅的使用线程的方式,即 QObject::moveToThread() 。但是即使在 2020 的今天,网上仍然有不少教程教我们使用Qt多线程的旧方法;难怪 Bradley T. Hughes2010 专门写了篇 You’re doing it wrong…,为此我只能表示:

在这里插入图片描述
在这里插入图片描述

new Object Class & moveToThread(new QThread)

下面介绍推荐做法。


/*--------------------DisconnectMonitor-------------------------*/
class DisconnectMonitor : public QObject
{Q_OBJECTpublic:explicit DisconnectMonitor();signals:void StartMonitor(long long hanlde);void StopMonitor();// if Controller disconnect emit this signalvoid Disconnect();private slots:void slot_StartMonitor(long long hanlde);void slot_StopMonitor();// State machinevoid Monitor();private:long long ControllerHanlde;QTimer *MonitorTimer;
};DisconnectMonitor::DisconnectMonitor()
{// New a Timer monitor controller by timingMonitorTimer = new QTimer;ControllerHanlde = 0;connect(MonitorTimer,&QTimer::timeout,this,&DisconnectMonitor::Monitor);connect(this,&DisconnectMonitor::StartMonitor,this,&DisconnectMonitor::slot_StartMonitor);connect(this,&DisconnectMonitor::StopMonitor,this,&DisconnectMonitor::slot_StopMonitor);MonitorTimer->start(TAKETIME);
}void DisconnectMonitor::Monitor(){// if not Controller -> returnif(0 == ControllerHanlde){return;}//else Listeningelse{int state = IsConnect(ControllerHanlde);if (0 != state){emit Disconnect();}}
}/*---------------------------Controller----------------------------*/
class Controller : public QObject
{Q_OBJECTQThread workerThread;
public:Controller() {DisconnectMonitor *monitor = new DisconnectMonitor;monitor->moveToThread(&workerThread);connect(workerThread, &QThread::finished, monitor, &QObject::deleteLater);connect(monitor,SIGNAL(Disconnect()),this,SLOT(DisconnectManage()));workerThread.start();}~Controller() {workerThread.quit();workerThread.wait();}
private slots:void DisconnectManage();
};

这里通过 moveToThread()Object 对象移到到新线程中,如此一来整个 monitor 都将在子线程中运行(其实这句话是有问题的,这是一个感性的理解)。

我们在 DisconnectMonitor 中定义了一个定时器用以实现定时检测。由于不能跨线程操作DisconnectMonitor 中的定时器,我们在类创建时就开启定时器,在超时事件中实现定时监听,如果检测到设备断开了,就发送 Disconnect() 信号。

使用 movetoThread() 需要注意的是:

  • 上面的案例中,并不能认为 monitor 的控制权归属于新线程!它仍然属于主线程,正如一位博主所说【在哪里创建就属于哪里】。movetoThread()的作用是将槽函数在指定的线程中调用。仅有槽函数在指定线程中调用,包括构造函数都仍然在主线程中调用!!!

  • DisconnectMonitor 须继承自 顶层 父类 Object,否则不能移动。

  • 如果 Threadnullptr,则该对象及其子对象的所有事件处理都将停止,因为它们不再与任何线程关联。

  • 调用 movetoThread() 时,移动对象的所有计时器将被重置。 计时器首先在当前线程中停止,然后在targetThread中重新启动(以相同的间隔),这时定时器属于子线程。若在线程之间不断移动对象可能会无限期地延迟计时器事件

QObject Class特别提醒movetoThread() 是线程不安全的,它只能见一个对象“推”到另一个线程,而不能将对象从任意线程推到当前线程,除非这个对象不再与任何线程关联。

connect

connect 函数原型如下:

static QMetaObject::Connection connect(const QObject *sender, const char *signal,const QObject *receiver, const char *member, Qt::ConnectionType = Qt::AutoConnection);
static QMetaObject::Connection connect(const QObject *sender, const QMetaMethod &signal,const QObject *receiver, const QMetaMethod &method,Qt::ConnectionType type = Qt::AutoConnection);
inline QMetaObject::Connection connect(const QObject *sender, const char *signal,const char *member, Qt::ConnectionType type = Qt::AutoConnection) const;

我们经常使用 connect,但是确很少留意最后一个参数 Qt::ConnectionType

  • Qt::AutoConnection
    默认连接类型,如果信号接收方与发送方在同一个线程,则使用 Qt::DirectConnection,否则使用 Qt::QueuedConnection;连接类型在信号 发射时 决定。

  • Qt::DirectConnection
    信号所连接至的槽函数将会被立即执行,并且是在发射信号的线程;倘若槽函数执行的是耗时操作、信号由 UI线程 发射,则会 阻塞 Qt的事件循环,UI会进入 无响应状态

  • Qt::QueuedConnection
    槽函数将会在接收者的线程被执行,此种连接类型下的信号倘若被多次触发、相应的槽函数会在接收者的线程里被顺次执行相应次数;当使用 QueuedConnection 时,参数类型必须是Qt基本类型,或者使用 qRegisterMetaType() 进行注册了的自定义类型。

  • Qt::BlockingQueuedConnection
    Qt::QueuedConnection 类似,区别在于发送信号的线程在槽函数执行完毕之前一直处于阻塞状态;收发双方必须不在同一线程,否则会导致 死锁

  • Qt::UniqueConnection
    执行方式与 AutoConnection 相同,不过关联是唯一的。(如果相同两个对象,相同的信号关联到相同的槽,那么第二次 connect 将失败)

注意
如果接受者线程中有一个事件循环,那么当发送者与接受者在不同的线程中时,使用 DirectConnection 是不安全的;类似的,调用其他线程中的对象的任何函数也是不安全的。值得留意的是,QObject::connect() 函数本身是线程安全的。

事件循环

若使用默认的 run() 方法或自行调用 exec() ,则QThread将开启事件循环。QThread 同样提供了 exit() 函数和 quit() 槽。这赋予了QThread使用需要事件循环的非GUI类的能力(QTimerQTcpSocket 等)。也使得该线程可以关联任意一个线程的信号到指定线程的槽函数。如果一个线程没有开启事件循环,那么该线程中的 timeout() 将永远不会发射。

如果在一个线程中创建了OBject 对象,那么发往这个对象的事件将由该线程的事件循环进行分派。

在这里插入图片描述
我们可以手动使用 QCoreApplication::postEvent() 在任何时间先任何对象发送事件,该函数是线程安全的。

源码分析

看到这,对线程的创建尚有困惑,于是查找了一下 Qt 的源码。目前在 qthread_win.cpp 找到答案,至于程序是如何从 QThread -> qthread_win 尚不清楚。

使用时,我们均以 QThread->start() 开启线程:

/*-----------------------qthread_win.cpp---------------------------------*/
void QThread::start(Priority priority)
{Q_D(QThread);QMutexLocker locker(&d->mutex);if (d->isInFinish) {locker.unlock();wait();locker.relock();}if (d->running)return;d->running = true;d->finished = false;d->exited = false;d->returnCode = 0;d->interruptionRequested = false;/*NOTE: we create the thread in the suspended state, set thepriority and then resume the thread.since threads are created with normal priority by default, wecould get into a case where a thread (with priority less thanNormalPriority) tries to create a new thread (also with priorityless than NormalPriority), but the newly created thread preemptsits 'parent' and runs at normal priority.*/// 【1】判断当前环境,调用系统API创建线程 d->handle 为线程句柄#if defined(Q_CC_MSVC) && !defined(_DLL) // && !defined(Q_OS_WINRT)#  ifdef Q_OS_WINRT#    error "Microsoft documentation says this combination leaks memory every time a thread is started. " \"Please change your build back to -MD/-MDd or, if you understand this issue and want to continue, " \"edit this source file."#  endif// MSVC -MT or -MTd buildd->handle = (Qt::HANDLE) _beginthreadex(NULL, d->stackSize, QThreadPrivate::start,this, CREATE_SUSPENDED, &(d->id));#else// MSVC -MD or -MDd or MinGW buildd->handle = CreateThread(nullptr, d->stackSize,reinterpret_cast<LPTHREAD_START_ROUTINE>(QThreadPrivate::start),this, CREATE_SUSPENDED, reinterpret_cast<LPDWORD>(&d->id));#endif // Q_OS_WINRT//创建线程失败if (!d->handle) {qErrnoWarning("QThread::start: Failed to create thread");d->running = false;d->finished = true;return;}//优先级int prio;d->priority = priority;switch (d->priority) {case IdlePriority:prio = THREAD_PRIORITY_IDLE;break;case LowestPriority:prio = THREAD_PRIORITY_LOWEST;break;case LowPriority:prio = THREAD_PRIORITY_BELOW_NORMAL;break;case NormalPriority:prio = THREAD_PRIORITY_NORMAL;break;case HighPriority:prio = THREAD_PRIORITY_ABOVE_NORMAL;break;case HighestPriority:prio = THREAD_PRIORITY_HIGHEST;break;case TimeCriticalPriority:prio = THREAD_PRIORITY_TIME_CRITICAL;break;case InheritPriority:default:prio = GetThreadPriority(GetCurrentThread());break;}if (!SetThreadPriority(d->handle, prio)) {qErrnoWarning("QThread::start: Failed to set thread priority");}if (ResumeThread(d->handle) == (DWORD) -1) {qErrnoWarning("QThread::start: Failed to resume new thread");}
}

核心为【1】我们先找找 _beginthreadex 原型,这是一个 Windows 系统 API

unsigned long _beginthreadex( 
void *security,       // 安全属性,NULL为默认安全属性
unsigned stack_size,  // 指定线程堆栈的大小。如果为0,则线程堆栈大小和创建它的线程的相同。一般用0
unsigned ( __stdcall *start_address )( void * ), // 指定线程函数的地址,也就是线程调用执行的函数地址(用函数名称即可,函数名称就表示地址)
void *arglist,        // 传递给线程的参数的指针,可以通过传入对象的指针,在线程函数中再转化为对应类的指针
unsigned initflag,    // 线程初始状态,0:立即运行;CREATE_SUSPEND:suspended(悬挂)
unsigned *thrdaddr    // 用于记录线程ID的地址

对应源码食用,可发现线程函数地址为 QThreadPrivate::start,跟踪一下:

unsigned int __stdcall QT_ENSURE_STACK_ALIGNED_FOR_SSE QThreadPrivate::start(void *arg) noexcept
{// 强制转换QThread *thr = reinterpret_cast<QThread *>(arg);QThreadData *data = QThreadData::get2(thr);qt_create_tls();TlsSetValue(qt_current_thread_data_tls_index, data);data->threadId.storeRelaxed(reinterpret_cast<Qt::HANDLE>(quintptr(GetCurrentThreadId())));QThread::setTerminationEnabled(false);{QMutexLocker locker(&thr->d_func()->mutex);data->quitNow = thr->d_func()->exited;}data->ensureEventDispatcher();
#if !defined(QT_NO_DEBUG) && defined(Q_CC_MSVC) && !defined(Q_OS_WINRT)// sets the name of the current thread.QByteArray objectName = thr->objectName().toLocal8Bit();qt_set_thread_name(HANDLE(-1),objectName.isEmpty() ?thr->metaObject()->className() : objectName.constData());
#endif//发射 started 信号emit thr->started(QThread::QPrivateSignal());QThread::setTerminationEnabled(true);//调用QThread,run函数thr->run();finish(arg);return 0;
}

可以发现调用了 QThreadrun() 方法。

而该方法默认开启事件循环:

void QThread::run()
{(void) exec();
}

这样我们的线程就跑起来了。

如何正确退出线程

首先,删除 QThread 对象并不会停止其管理的线程的执行。删除正在运行的 QThread 将导致 程序奔溃。在删除 QThread 之前我们需要等待 finish 信号。

  • 对于未开启事件循环的线程,我们仅需让 run() 执行结束即可终止线程,常见的做法是通过 bool 变量进行控制。由于我们的 bool runenanble 被多线程访问,这里我们需要定义一个 QMutex 进行加锁保护。至于加锁的效率问题,网上有大佬测出大概速度会降低1.5倍(Release模式)

    void TestThread::stopThread(){mutex.lock();runenanble = false;mutex.unlock();}void TestThread::run(){runenanble = true;while(1){if(mutex.tryLock()){if(!runenable)break;else{/*dosomething*/}}}
    }
    
  • 对于开启了事件循环的线程,正常的退出线程其实质是退出事件循环。

    • quit()/exit() + wait()
      若线程中开始开启了 EvenLoop,耗时代码执行结束后,线程并不会退出。我们可调用 quit()/exit() + wait() 实现退出。

    • terminate()+ wait()
      调用 terminate() 后,将根据操作系统的调度,线程可能立即结束也可能不会,终止之后仍需使用 wait()
      由于线程可能在任何位置终止,强制结束线程是危险的操作,可能在修改数据数据时终止,可能导致线程状态无法清除,可能导致锁异常。因此并 不推荐使用

    • finished
      仅依靠上面的方法退出线程,可能存在 内存泄漏 的情况。注意到官方案例中都使用了finished 信号:

      connect(workerThread, &WorkerThread::finished, workerThread, &QObject::deleteLater);
      

      如果类对象保存在 上,自然销毁由操作系统自动完成;如果是保存在 上,没有父对象的指针要想正常销毁,需要自行释放。
      Qt4.8 开始,我们就可以通过将 finished() 信号链接至 Object::deleteLater() 来释放刚刚结束的线程中的对象。

      上文例二的 QThread 并未 new 出来,这样在析构时就需要调用 Thread::wait(),如果是堆分配的话, 可以通过 deleteLater 来让线程自杀。

      QThread workerThread = new QThread();
      connect(workerThread, &QThread::finished, workerThread, &QObject::deleteLater);
      

注意:程序退出前,需要判断各线程是否已退出,如果不进行判断,很可能程序退出时会崩溃。如果线程的父对象是窗口对象,那么在窗体的析构函数中,还需要调用 wait() 等待线程完全结束再进行下面的析构。

堆栈大小

大多数操作系统都为线程堆栈设置了最大和最小限制。如果超出这些限制,线程将无法启动。

每个线程都有自己的栈,彼此独立,由编译器分配。一般在 Windows 的栈大小为 2M,在 Linux 下是 8M

Qt 提供了获取以及设置栈空间大小的函数:stackSize()setStackSize(uint stackSize)。其中 stackSize() 函数不是返回当前所在线程的栈大小,而是获取用 stackSize() 函数手动设置的栈大小。

优先级

没错,QThread 不再让线程间拼得你死我活,我们可以通过 setPriority() 设置线程优先级,通过 priority() 获取线程优先级。

ConstantValueDescription
QThread::IdlePriority0scheduled only when no other threads are running.
QThread::LowestPriority1scheduled less often than LowPriority.
QThread::LowPriority2scheduled less often than NormalPriority.
QThread::NormalPriority3the default priority of the operating system.
QThread::HighPriority4scheduled more often than NormalPriority.
QThread::HighestPriority5scheduled more often than HighPriority.
QThread::TimeCriticalPriority6scheduled as often as possible.
QThread::InheritPriority7use the same priority as the creating thread. This is the default.

此外,QThread 类还提供了 yieldCurrentThread() 静态函数,该函数是在通知操作系统“我这个线程不重要,优先处理其他线程吧”。当然,调用该函数后不会立马将 CPU 计算资源交出去,而是由操作系统决定。

QThread 类还提供了 sleep()msleep()usleep() 这三个函数,这三个函数也是在通知操作系统“在未来 time 时间内我不参与 CPU 计算”。

值得注意的是:usleep()不能保证准确性 。某些OS可能将舍入时间设置为10us/15us;在 Windows 上它将四舍五入为 1ms 的倍数。

线程间通讯

其实上文已经演示了两种方式:

  • 共享内存
    线程隶属于某一个进程,与进程内的其他线程一起共享这片地址空间。

  • 消息传递
    借助Qt的信号槽&事件循环机制。

说个题外话,在 Android 中,UI操作只能在主线程中进行。这种情况在Qt中其实类似,那么当我们子线程需要更新UI控件时怎么处理呢?很简单发送信号让主线程更新即可~

线程同步

虽然使用多线程的思想是让程序尽可能并发执行,但是总有一些时候,线程必须停止以等待其他线程。例如两个线程同时写全局变量,由于写入操作相对于CPU不具备原子性,结果通常具有不确定性。

互斥锁

QMutex,任意时刻至多有一个线程可以使用该锁,若一个线程尝试获取 mutex ,而此时 mutex 已被锁住。则这儿线程将休眠直到 mutex解锁 为止。互斥锁经常用于共享数据。

QMutex mutex;void thread1()
{mutex.lock();//dosomething()mutex.unlock();
}void thread2()
{mutex.lock();//dosomething()mutex.unlock();
}

读写锁

QReadWriteLock,与 QMutex 类似,不过它允许多个线程对共享数据进行读取。使用它替代 QMutex 可提高多线程程序的并发度。

QReadWriteLock lock;void ReaderThread::run()
{...lock.lockForRead();read_file();lock.unlock();...
}void WriterThread::run()
{...lock.lockForWrite();write_file();lock.unlock();...
}

信号量

QSemaphoreQMutex 的一般化,用于保护一定数量的相同的资源。典型的是 生成者-消费者

QSemaphore sem(5);      // sem.available() == 5sem.acquire(3);         // sem.available() == 2
sem.acquire(2);         // sem.available() == 0
sem.release(5);         // sem.available() == 5
sem.release(5);         // sem.available() == 10sem.tryAcquire(1);      // sem.available() == 9, returns true
sem.tryAcquire(250);    // sem.available() == 9, returns false

条件变量

QWaitCondition ,它允许一个线程在一些条件满足的情况下唤醒其他线程。

QWaitCondition Class 中列举一个接收按键并唤醒去其他线程进行处理的 demo:

forever {mutex.lock();keyPressed.wait(&mutex);++count;mutex.unlock();do_something();mutex.lock();--count;mutex.unlock();
}forever {getchar();mutex.lock();// Sleep until there are no busy worker threadswhile (count > 0) {mutex.unlock();sleep(1);mutex.lock();}keyPressed.wakeAll();mutex.unlock();
}

可重入与线程安全

在查看 Qt Class 文档时,有时候我们会看到线程安全和可重入的标记,什么是线程安全,什么是可重入?

  • 线程安全
    表示该函数可被多个线程调用,即使他们使用了共享数据,因为该共享数据的所有实例都被序列化了。

  • 可重入
    一个可重入的函数可被多个线程调用,但是只能是使用自己数据的情况下。

如果每个线程使用一个类的不同实例,该类的成员函数可以被多个线程安全地调用,那么该类被称为可重入的;如果所有线程使用该类的相同实例,该类的成员函数也可以被多个线程安全地调用,那么该类是线程安全的。

在这里插入图片描述

QObject的可重入性

QObject 是可重入的。它的大多数 非GUI子类,如 QTimerQTcpSocket 也都是可重入的,可以在多线程中使用。值得注意的是,这些类被设计成在单一线程中进行创建和使用,在一个线程中创建一个对象,然后在另一个线程中调用这个对象的一个函数是无法保证一定可以工作的。需要满足以下三个条件:

  • QObject 的子对象必须在创建它的父对象的线程中创建。这意味这不要将 QThread 对象 (this) 作为在该线程中创建的对象的父对象。
  • 事件驱动对象只能在单一线程中使用。例如:不可以在对象所在的线程以外的其他线程中启动一个定时器或连接套接字。
  • 必须保证在删除 QThread 对象以前,删除在该线程中创建的所有对象。

对于大部分 GUI类,尤其是 QWidget及其子类,都是不可重入的,我们只能在主线程中使用。QCoreApplication::exec() 也必须在主线程中调用。

开启多少个线程合理

线程的切换是要消耗系统资源的,频繁的切换线程会使性能降低。线程太少的话又不能完全发挥 CPU 的性能。

一般后端服务器都会设置最大工作线程数,不同的架构师有着不同的经验,有些业务设置为 CPU 逻辑核心数的4倍,有的甚至达到32倍

Venkat Subramaniam 博士的 《Programming Concurrency on the JVM》 这本书中提到关于最优线程数的计算,即:

线 程 数 量 = 可 用 核 心 数 / ( 1 − 阻 塞 系 数 ) 线程数量 = 可用核心数/(1 - 阻塞系数) 线=/(1)

可用核心数就是所有逻辑 CPU 的总数,这可以用 QThread::idealThreadCount() 静态函数获取,比如双核四线程的 CPU 的返回值就是4。

但是阻塞系数比较难计算,这需要用一些性能分析工具来辅助计算。如果只是粗浅的计算下线程数,最简单的办法就是 CPU 核心数 * 2 + 2 。更为精细的找到最优线程数需要不断的调整线程数量来观察系统的负载情况。

参考鸣谢

Qt Creator快速入门

Qt5.9 C++开发指南

Qt多线程编程爬坑笔记

Qt使用多线程的一些心得——1.继承QThread的多线程使用方法

Qt使用多线程的一些心得——2.继承QObject的多线程使用方法

Qt 多线程编程之敲开 QThread 类的大门

QThread源码浅析


http://chatgpt.dhexx.cn/article/WiICHXq5.shtml

相关文章

Qt 线程中QThread的使用

文章目录 Qt 线程中QThread的使用1. 线程类 QThread1.1 常用共用成员函数1.2 信号槽1.3 静态函数1.4 任务处理函数 2. 使用方式 12.2 示例代码3. 使用方式 23.1 操作步骤3.2 示例代码 Qt 线程中QThread的使用 在进行桌面应用程序开发的时候&#xff0c; 假设应用程序在某些情况…

Qt之QThread(深入理解)

简述 为了让程序尽快响应用户操作&#xff0c;在开发应用程序时经常会使用到线程。对于耗时操作如果不使用线程&#xff0c;UI界面将会长时间处于停滞状态&#xff0c;这种情况是用户非常不愿意看到的&#xff0c;我们可以用线程来解决这个问题。 前面&#xff0c;已经介绍了…

Qt之QThread介绍(常用接口及实现、自动释放内存、关闭窗口时停止线程运行、同步互斥)

在程序设计中&#xff0c;为了不影响主程序的执行&#xff0c;常常把耗时操作放到一个单独的线程中执行。Qt对多线程操作有着完整的支持&#xff0c;Qt中通过继承QThread并重写run()方法的方式实现多线程代码的编写。针对线程之间的同步与互斥问题&#xff0c;Qt还提供了QMutex…

Qt线程QThread开启和安全退出

1、线程开启 Qt中&#xff0c;开启子线程&#xff0c;一般有两种方法&#xff1a; a, 定义工作类worker: worker继承 QThread, 重写run函数&#xff0c;在主线程中实例化worker&#xff0c;把耗时工作放进worker的run函数中完成&#xff0c;结束后&#xff0c;往主线程中发信…

QThread的用法

概述 QThread类提供了一个与平台无关的管理线程的方法。一个QThread对象管理一个线程。QThread的执行从run()函数的执行开始&#xff0c;在Qt自带的QThread类中&#xff0c;run()函数通过调用exec()函数来启动事件循环机制&#xff0c;并且在线程内部处理Qt的事件。在Qt中建立线…

Oracle 定时任务执行存储过程【建议收藏】

首先用一个完整的例子来实现定时执行存储过程。 任务目标&#xff1a;每小时向test表中插入一条数据。 实现方案&#xff1a; 1.通过 oracle 中 dbms_job 完成存储过程的定时调用 2.在存储过程中完成相应的逻辑操作 实现步骤&#xff1a; 1.创建一个测试表 create table test…

【Mysql】MySQL 用户执行存储过程的权限

问题 运行存储过程报错&#xff1a; 原因 查询资料&#xff1a; 1305错误&#xff0c;由于当前用户没用权限&#xff0c;对用户进行授权后可以执行。 解决 MySQL创建存储过程/函数需要的权限&#xff1a; alter routine---修改与删除存储过程/函数 create routine--创建…

goland 使用 gorm 执行 存储过程 : go语言 执行存储过程

使用 gorm 执行 存储过程 初安装依赖代码&#xff1a; 附存储过程图片存储过程代码&#xff08;创建&#xff09; 表结构表结构图表结构代码 初 最近遇到要写存储过程需求&#xff0c;使用 大佬写的 框架 gorm 来完成。简直是方便的不行&#xff1a; 直接上代码&#xff1a; …

JDBC之CallableStatement执行存储过程

​ 在前面的一篇文章中&#xff0c;我们学习使用Statement、PreparedStatement来完成对数据表的增删改查。而存储过程作为数据库的重要组成部分&#xff08;痛点&#xff0c;当时学的时候头发都掉了好几根&#x1f62d;&#xff09;&#xff0c;那JDBC是如何执行存储过程呢&…

mysql创建定时任务执行存储过程

存储过程已添加好&#xff1a;https://blog.csdn.net/YXWik/article/details/127283316 1.创建定时器用来执行存储过程函数 create event delete_data on schedule every 10 second do call delete_data();这里的第一行代表的创建名称为delete_data的事件 第二行是执行周期为…

Oracle 定时任务执行存储过程

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、创建存储过程一、创建定时任务 一、创建存储过程 CREATE OR REPLACE PROCEDURE TESTCREATE AS --存储过程名称I INT : 1; --变量 BEGINWHILE I < 10 LOOP -…

oracle执行存储过程参数,Oracle 执行存储过程四种方法(带参数 不带参数)

1.如果是命令窗口就用exec 存储过程名&#xff1a; 1 EXEC procedure;--procedure是存储过程名 2.如果是 SQL窗口就用 begin 存储过程名 end; 1 2 3 begin procedure;--procedure是存储过程名 end; 3.如果是程序中调用就用 call 存储过程名 &#xff0c;举个栗子&#xff1…

plsql定时执行存储过程

1、创建定时向表中插入数据的存储过程&#xff0c;名为testJob。 CREATE OR REPLACE PROCEDURE "testJob" AS BEGIN EXECUTE IMMEDIATE INSERT INTO TABLE_HIS SELECT * FROM TABLE_AI; COMMIT; END; 2、使用plsql找到定时器对应的DBMS_Jobs文件夹&#xff0c;…

SQLserver存储过程简单写法与设置定时执行存储过程方法

最近工作中需要写SQLserver的存储过程&#xff0c;第一次使用&#xff0c;简单记录下&#xff0c;以防遗忘。 在SQLserver可视化工具中编写&#xff0c;我的工具如下图&#xff1a; 首先点击你的数据库&#xff0c;找到可编程性&#xff0c;在可编程性里面右击存储过程-->点…

symlink() 函数

查看更多 https://www.yuque.com/docs/share/10f959a4-bd7e-47a9-ad78-11a1310613f3

Install fail! Error: EPERM: operation not permitted, symlink

这个是在安装lodash的时候报错的情况 尝试了下方命令&#xff0c;但是还是有误 在这里可能是因为npm缓存的问题&#xff0c;我先删除了 C:\Users\abc(自己电脑的用户名) 文件夹下的 .npmrc 文件&#xff0c;但是没反应。然后运行了下方的命令&#xff1a; 运行&#xff1a;…

error: eperm: operation not permitted, symlink

查询当前配置的镜像 npm get registry > https://registry.npmjs.org/ 设置成淘宝镜像 npm config set registry http://registry.npm.taobao.org/

Linux Symbolic Links(软链接)

linux 中的链接有两种&#xff0c;分别是 Hard Links&#xff08;硬链接&#xff09;和Symbolic Links(软链接)。 1. 什么是硬链接、软链接&#xff1f; 硬链接&#xff1a;在另外一个位置创建源文件的链接文件&#xff0c;相当于复制了一份&#xff0c;占用资源会倍增。硬链…

linux link/symlink/unlink 硬连接和软连接介绍

文章目录 硬连接和软连接的区别硬连接软连接 链接命令lnln指令参数含义例子 硬链接函数link()符号链接函数symlink()解除链接函数unlink() link/symlink/unlink函数头文件为#include <unistd.h> 硬连接和软连接的区别 硬连接 硬连接指通过索引节点来进行连接。 在Lin…

​2021-12-13 Android 的 init.rc 文件​里面的symlink,把<target>链接到目录<path>下。

一、symlink <target> <path>&#xff0c;把<target>链接到目录<path>下。 二、来看一下实际的例子 三、参考文章&#xff1a; Android 的 init.rc 文件简介 &#xff08;转&#xff09; - 陈wei的个人空间 - OSCHINA - 中文开源技…