14.QueuedConnection和BlockingQueuedConnection连接方式源码分析

2023-11-17

QT信号槽直连时的时序和信号槽的连接方式已经在前面的文章中分析过了,见https://blog.csdn.net/Master_Cui/article/details/109011425https://blog.csdn.net/Master_Cui/article/details/109228521,本文对QueuedConnection和BlockingQueuedConnection连接方式进行源码分析

当信号槽的连接方式是QueuedConnection

if ((c->connectionType == Qt::AutoConnection && !receiverInSameThread) || (c->connectionType == Qt::QueuedConnection)) {
                queued_activate(sender, signal_index, c, argv);
                continue;
#if QT_CONFIG(thread)
}

上述代码表明:当信号槽的连接方式是QueuedConnection时,直接调用的是queued_activate,然后 continue 进行下次循环;

看下先queued_activate的实现

static void queued_activate(QObject *sender, int signal, QObjectPrivate::Connection *c, void **argv)
{
    const int *argumentTypes = c->argumentTypes.loadRelaxed();//获取connection中的信号函数的参数类型
    if (!argumentTypes) {//如果信号函数的参数类型为空,重新获取
        QMetaMethod m = QMetaObjectPrivate::signal(sender->metaObject(), signal);//根据发送信号的元对象和和信号索引获取信号函数
        argumentTypes = queuedConnectionTypes(m.parameterTypes());//将信号函数的参数封装在一个数组中并返回数组的首地址
        if (!argumentTypes) // cannot queue arguments//异常检测
            argumentTypes = &DIRECT_CONNECTION_ONLY;
        if (!c->argumentTypes.testAndSetOrdered(0, argumentTypes)) {
            if (argumentTypes != &DIRECT_CONNECTION_ONLY)
                delete [] argumentTypes;
            argumentTypes = c->argumentTypes.loadRelaxed();
        }
    }
    if (argumentTypes == &DIRECT_CONNECTION_ONLY) // cannot activate//异常检测
        return;
    int nargs = 1; // include return type
    while (argumentTypes[nargs-1])//获取信号参数数组中参数的个数
        ++nargs;

    QBasicMutexLocker locker(signalSlotLock(c->receiver.loadRelaxed()));
    if (!c->receiver.loadRelaxed()) {
        // the connection has been disconnected before we got the lock
        return;
    }
    if (c->isSlotObject)
        c->slotObj->ref();
    locker.unlock();

    QMetaCallEvent *ev = c->isSlotObject ?//如果connection中包含一个有用槽函数指针的对象,那么,对应QT5的新语法
    //如果connection中仅仅是一个函数指针callFunction,对应QT4语法,这样就能让QT同时支持两种语法
        new QMetaCallEvent(c->slotObj, sender, signal, nargs) :
        new QMetaCallEvent(c->method_offset, c->method_relative, c->callFunction, sender, signal, nargs);

    void **args = ev->args();
    int *types = ev->types();

    types[0] = 0; // return type
    args[0] = nullptr; // return value

    if (nargs > 1) {
        for (int n = 1; n < nargs; ++n)
            types[n] = argumentTypes[n-1];//将argumentTypes中的参数类型填充到QMetaCallEvent中

        for (int n = 1; n < nargs; ++n)
            args[n] = QMetaType::create(types[n], argv[n]);//利用参数类型和参数的序号找到参数对应的值,然后填充到QMetaCallEvent中
    }
    //填充到QMetaCallEvent中的参数类型和参数值主要是因为:信号参数的指针数组在栈上。一旦信号退出,这些指针数组将不再有效。

    locker.relock();
    if (c->isSlotObject)
        c->slotObj->destroyIfLastRef();
    if (!c->receiver.loadRelaxed()) {
        // the connection has been disconnected while we were unlocked
        locker.unlock();
        delete ev;
        return;
    }

    QCoreApplication::postEvent(c->receiver.loadRelaxed(), ev);//将拥有参数类型和参数值的QMetaCallEvent放到事件队列中
}

所以,queued_activate的基本思路就是将信号的参数信息放到QMetaCallEvent中,然后放到循环事件队列中等待发送

QMetaCallEvent的定义如下

class Q_CORE_EXPORT QMetaCallEvent : public QAbstractMetaCallEvent
{
public:
    // blocking queued with semaphore - args always owned by caller
    QMetaCallEvent(ushort method_offset, ushort method_relative,
                   QObjectPrivate::StaticMetaCallFunction callFunction,
                   const QObject *sender, int signalId,
                   void **args, QSemaphore *semaphore);
    QMetaCallEvent(QtPrivate::QSlotObjectBase *slotObj,
                   const QObject *sender, int signalId,
                   void **args, QSemaphore *semaphore);

    // queued - args allocated by event, copied by caller
    QMetaCallEvent(ushort method_offset, ushort method_relative,
                   QObjectPrivate::StaticMetaCallFunction callFunction,
                   const QObject *sender, int signalId,
                   int nargs);
    QMetaCallEvent(QtPrivate::QSlotObjectBase *slotObj,
                   const QObject *sender, int signalId,
                   int nargs);
                   
    //四个构造函数分别对象QT4和QT5的语法,并且分别对应QueuedConnection和BlockingQueuedConnection的连接方式
    ~QMetaCallEvent() override;

    inline int id() const { return d.method_offset_ + d.method_relative_; }
    inline const void * const* args() const { return d.args_; }//该函数返回一个const指针,该const指针指向一个const void的指针
    inline void ** args() { return d.args_; }
    inline const int *types() const { return reinterpret_cast<int*>(d.args_ + d.nargs_); }
    inline int *types() { return reinterpret_cast<int*>(d.args_ + d.nargs_); }

    virtual void placeMetaCall(QObject *object) override;

private:
    inline void allocArgs();

    struct Data {
        QtPrivate::QSlotObjectBase *slotObj_;//成员槽函数指针的对象的指针
        void **args_;//指向存储参数指针的数组的二级指针
        QObjectPrivate::StaticMetaCallFunction callFunction_;//函数指针,就是MOC文件中的qt_static_metacall
        int nargs_;//参数的个数
        ushort method_offset_;//信号方法的偏移
        ushort method_relative_;//信号方法的相对序号
    } d;
    // preallocate enough space for three arguments
    char prealloc_[3*(sizeof(void*) + sizeof(int))];
};

QCoreApplication::postEvent的实现如下

void QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority)
{
    Q_TRACE_SCOPE(QCoreApplication_postEvent, receiver, event, event->type());

    if (receiver == 0) {//异常处理
        qWarning("QCoreApplication::postEvent: Unexpected null receiver");
        delete event;
        return;
    }

    QThreadData * volatile * pdata = &receiver->d_func()->threadData;
    QThreadData *data = *pdata;//获取接收者所在的线程数据
    if (!data) {
        // posting during destruction? just delete the event to prevent a leak
        delete event;
        return;
    }

    // lock the post event mutex
    data->postEventList.mutex.lock();
    //事件将发送到每个线程的事件队列(QThreadData::postEventList)中。 
    //排队的事件受互斥锁保护,因此当线程将事件推送到另一个线程的事件队列时,没有竞争条件。

    // if object has moved to another thread, follow it//接收者有可能被移动到其他线程中运行,那么将线程数据的指针重新指向
    while (data != *pdata) {
        data->postEventList.mutex.unlock();

        data = *pdata;
        if (!data) {
            // posting during destruction? just delete the event to prevent a leak
            delete event;
            return;
        }

        data->postEventList.mutex.lock();
    }

    QMutexUnlocker locker(&data->postEventList.mutex);

    // if this is one of the compressible events, do compression
    if (receiver->d_func()->postedEvents
        && self && self->compressEvent(event, receiver, &data->postEventList)) {
        Q_TRACE(QCoreApplication_postEvent_event_compressed, receiver, event);
        return;
    }
    //compressEvent作用是判断一个事件是否已经被删除,如果被删除,则不应该添加到事件队列中

    if (event->type() == QEvent::DeferredDelete)//关于DeferredDelete事件的处理,暂时不用管
        receiver->d_ptr->deleteLaterCalled = true;

    if (event->type() == QEvent::DeferredDelete && data == QThreadData::current()) {//关于DeferredDelete事件的处理,暂时不用管
        // remember the current running eventloop for DeferredDelete
        // events posted in the receiver's thread.

        // Events sent by non-Qt event handlers (such as glib) may not
        // have the scopeLevel set correctly. The scope level makes sure that
        // code like this:
        //     foo->deleteLater();
        //     qApp->processEvents(); // without passing QEvent::DeferredDelete
        // will not cause "foo" to be deleted before returning to the event loop.

        // If the scope level is 0 while loopLevel != 0, we are called from a
        // non-conformant code path, and our best guess is that the scope level
        // should be 1. (Loop level 0 is special: it means that no event loops
        // are running.)
        int loopLevel = data->loopLevel;
        int scopeLevel = data->scopeLevel;
        if (scopeLevel == 0 && loopLevel != 0)
            scopeLevel = 1;
        static_cast<QDeferredDeleteEvent *>(event)->level = loopLevel + scopeLevel;
    }

    // delete the event on exceptions to protect against memory leaks till the event is
    // properly owned in the postEventList
    QScopedPointer<QEvent> eventDeleter(event);//在处理事件的线程中对其进行处理后,将立即删除该事件
    Q_TRACE(QCoreApplication_postEvent_event_posted, receiver, event, event->type());
    data->postEventList.addEvent(QPostEvent(receiver, event, priority));//将事件放入循环队列中
    eventDeleter.take();
    event->posted = true;
    ++receiver->d_func()->postedEvents;
    data->canWait = false;
    locker.unlock();

    QAbstractEventDispatcher* dispatcher = data->eventDispatcher.loadAcquire();//取得eventDispatcher,Linux下对应的是QEventDispatcherGlib的指针
    if (dispatcher)
        dispatcher->wakeUp();//唤醒事件循环迭代上下文mainContext
}

void QEventDispatcherGlib::wakeUp()
{
    Q_D(QEventDispatcherGlib);
    d->postEventSource->serialNumber.ref();
    g_main_context_wakeup(d->mainContext);
}

g_main_context_wakeup会唤醒事件循环,处理事件队列中的事件,关于事件的分析见https://blog.csdn.net/master_cui/category_10463698.html

所以,postEvent实际上是将事件放到对应线程的事件循环中,所以当我们使用QueuedConnection对信号槽进行连接时,会先执行信号函数所在线程的代码,槽函数此时在对应线程的循环队列中等待,等信号函数所在线程的循环队列没有事件时,再执行槽函数所在线程的代码

通过事件循环机制,最终,事件会被事件处理函数接受被处理,而遇到QEvent::MetaCall时,以bool QObject::event(QEvent *e)为例,该类型的事件处理方式如下

bool QObject::event(QEvent *e)
{
    //..........
    case QEvent::MetaCall:
    {
        QAbstractMetaCallEvent *mce = static_cast<QAbstractMetaCallEvent*>(e);

        if (!d_func()->connections.loadRelaxed()) {
            QBasicMutexLocker locker(signalSlotLock(this));
            d_func()->ensureConnectionData();
        }
        QObjectPrivate::Sender sender(this, const_cast<QObject*>(mce->sender()), mce->signalId());

        mce->placeMetaCall(this);
        break;
    }
    //..........
}

上述代码的核心就是那句mce->placeMetaCall(this);

而placeMetaCall是个纯虚函数,在子类QMetaCallEvent中实现,代码如下

void QMetaCallEvent::placeMetaCall(QObject *object)
{
    if (d.slotObj_) {
        d.slotObj_->call(object, d.args_);
    } else if (d.callFunction_ && d.method_offset_ <= object->metaObject()->methodOffset()) {
        d.callFunction_(object, QMetaObject::InvokeMetaMethod, d.method_relative_, d.args_);
    } else {
        QMetaObject::metacall(object, QMetaObject::InvokeMetaMethod,
                              d.method_offset_ + d.method_relative_, d.args_);
    }
}

可见,当connect是对应QT5的语法时,调用的是d.slotObj_->call(object, d.args_);,直接进行函数调用,而当connect是对应QT4的语法时,直接调用函数指针,而函数指针指向的是MOC文件中的qt_static_metacall,具体见博客https://blog.csdn.net/Master_Cui/article/details/109011153https://blog.csdn.net/Master_Cui/article/details/109011218

当信号槽的连接方式是BlockingQueuedConnection时的代码如下

 else if (c->connectionType == Qt::BlockingQueuedConnection) {
                if (receiverInSameThread) {
                    qWarning("Qt: Dead lock detected while activating a BlockingQueuedConnection: "
                    "Sender is %s(%p), receiver is %s(%p)",
                    sender->metaObject()->className(), sender,
                    receiver->metaObject()->className(), receiver);
                }
                QSemaphore semaphore;
                {
                    QBasicMutexLocker locker(signalSlotLock(sender));
                    if (!c->receiver.loadAcquire())
                        continue;
                    QMetaCallEvent *ev = c->isSlotObject ?
                        new QMetaCallEvent(c->slotObj, sender, signal_index, argv, &semaphore) :
                        new QMetaCallEvent(c->method_offset, c->method_relative, c->callFunction,
                                           sender, signal_index, argv, &semaphore);
                    QCoreApplication::postEvent(receiver, ev);
                }
                semaphore.acquire();
                continue;
#endif
}

而当信号槽的连接方式是BlockingQueuedConnection时,直接通过事件循环机制将MetaCall事件放入循环队列中发送。只不过用信号量保护起来

上述代码的信号量的资源数为0,当信号槽都处于同一个线程时,信号先调用,执行到BlockingQueuedConnection分支时,QCoreApplication::postEvent(receiver, ev);调用结束后立刻返回,接着会执行semaphore.acquire();,acquire的默认实参是1,所以,此时线程会阻塞,又因为槽函数也处在当前线程(的事件队列)中,所以,槽函数也没法执行,而该信号量还没有release操作,所以,当信号槽处于同一线程且连接方式是BlockingQueuedConnection,会造成死锁。

而当信号和槽函数处于两个不同的线程时,虽然semaphore.acquire()会导致信号所在的线程阻塞,但不会导致槽函数所在的线程阻塞,当QMetaCallEvent被事件处理程序处理后,会立刻被删除,释放内存,而QMetaCallEvent的父类是QAbstractMetaCallEvent(是个纯虚基类),QAbstractMetaCallEvent的析构函数如下

QAbstractMetaCallEvent::~QAbstractMetaCallEvent()
{
#if QT_CONFIG(thread)
    if (semaphore_)
        semaphore_->release();
#endif
}

利用C++的多态机制,当QMetaCallEvent在槽函数的线程被销毁时,会调用QAbstractMetaCallEvent的析构函数,在析构函数中调用semaphore_->release();将信号量的资源释放,从而使得信号函数所在的线程的阻塞状态解除

所以,当信号槽的连接方式是BlockingQueuedConnection,如果信号槽处于同一线程,那么因为信号量的原因,会造成程序的死锁,但是如果信号槽处于不同线程,那么当槽函数所在的线程将会将信号量的资源释放掉,所以信号线程会先因为信号量阻塞,等待槽函数执行结束后将信号量的资源释放,接着信号线程获得信号量,然后继续执行

关于信号量见官方文档:https://doc.qt.io/qt-5/qsemaphore.html#details

官方文档中有个比喻特别好:信号量就像在餐厅用餐。 餐厅中的椅子数量就是信号量的资源。 当客人到餐厅吃饭时,会占座位。 然后椅子资源会递减。当没有椅子时,再来的人们就需要等待有椅子空出来。 随着人们的离开,椅子数会增加,从而允许更多的人进入餐厅吃饭。 但是,如果有10个人去吃饭,但只有9个座位,则这10人将一直等待,除非信号量添加新的资源(餐厅加新的椅子)。如果来了4个人吃饭,那么此时可用座位只有5个,那10个人的等待时间会更长。

 

参考

https://doc.qt.io/qt-5/qsemaphore.html#details

https://woboq.com/blog/how-qt-signals-slots-work-part3-queuedconnection.html

 

欢迎大家评论交流,作者水平有限,如有错误,欢迎指出

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

14.QueuedConnection和BlockingQueuedConnection连接方式源码分析 的相关文章

  • 与 Qt 项目的静态链接

    我有一个在 Visual Studio 2010 Professional 中构建的 Qt 项目 但是 当我运行它 在调试或发布模式下 时 它会要求一些 Qt dll 如果我提供 dll 并将它们放入 System32 中 它就可以工作 但
  • 指向特征矩阵的指针数组

    我在代码中使用 Eigen 的 MatrixXd 矩阵 在某个时刻我需要一个 3D 矩阵 由于 Eigen 没有三维矩阵类型 因为它仅针对线性代数进行了优化 因此我创建了一个 MatrixXd 类型的指针数组 Eigen MatrixXd
  • GCC 和 ld 找不到导出的符号...但它们在那里

    我有一个 C 库和一个 C 应用程序 尝试使用从该库导出的函数和类 该库构建良好 应用程序可以编译 但无法链接 我得到的错误遵循以下形式 app source file cpp text 0x2fdb 对 lib namespace Get
  • 动态生成的控件 ID 返回为 NULL

    我可以在 Page PreInit 函数中创建动态控件 如何检索控件及其 ID 我的 C 代码用于创建动态控件之一 var btn new WebForms Button btn Text btn ID Addmore btn Click
  • 如何在 QTabWidget Qt 中展开选项卡

    我有一个QTabWidget像这个 但我想展开选项卡以 填充 整个小部件宽度 如下所示 我怎样才能做到这一点 我在用Qt 5 3 2 and Qt 创建者 3 2 1 Update 我尝试使用setExpanding功能 ui gt myT
  • 从 WebBrowser 控件 C# 获取滚动值

    我试图在 WebBrowser 控件中获取网页的 Y 滚动索引 但无法访问内置滚动条的值 有任何想法吗 对于标准模式下的 IE 使用文档类型 正如你所说 scrollTop是的财产元素 而不是 HtmlDocument htmlDoc th
  • 为什么 set_symmetry_difference 无法与比较器一起使用?

    Example program include
  • AES 输出是否小于输入?

    我想加密一个字符串并将其嵌入到 URL 中 因此我想确保加密的输出不大于输入 AES 是可行的方法吗 不可能创建任何始终会创建比输入更小的输出的算法 但可以将任何输出反转回输入 如果您允许 不大于输入 那么基本上您只是在谈论同构算法alwa
  • 退出 Qt 程序的正确方法?

    我应该如何退出 Qt 程序 例如在加载数据文件时 发现文件损坏 并且用户需要退出该应用程序或重新启动数据文件 我是不是该 call exit EXIT FAILURE call QApplication quit call QCoreApp
  • 运行选定的代码生成器时出错:“未将对象引用设置到对象的实例。”错误?

    我已经尝试了所有解决方案 例如修复 VS 2013 但没有用 当您通过右键单击控制器文件夹来创建控制器并添加控制器时 然后右键单击新创建的控制器的操作并选择添加视图 当我尝试创建视图时 就会发生这种情况 它不是一个新项目 而是一个现有项目
  • 如何通过 JsonConvert.DeserializeObject 在动态 JSON 中使用 null 条件运算符

    我正在使用 Newtonsoft 反序列化已知的 JSON 对象并从中检索一些值 如果存在 关键在于对象结构可能会不断变化 因此我使用动态来遍历结构并检索值 由于对象结构不断变化 我使用 null 条件运算符来遍历 JSON 代码看起来像这
  • 每个租户的唯一用户名和电子邮件

    我正在使用以下代码编写多租户应用程序ASP NET Core 2 1 我想覆盖默认的与用户创建相关的验证机制 目前我无法创建多个具有相同的用户UserName My ApplicationUser模型有一个名为TenantID 我想要实现的
  • 如何分析组合的 python 和 c 代码

    我有一个由多个 python 脚本组成的应用程序 其中一些脚本正在调用 C 代码 该应用程序现在的运行速度比以前慢得多 因此我想对其进行分析以查看问题所在 是否有工具 软件包或只是一种分析此类应用程序的方法 有一个工具可以将 python
  • .NET Core 中的跨平台文件名处理

    如何处理文件名System IO以跨平台方式运行类以使其在 Windows 和 Linux 上运行 例如 我编写的代码在 Windows 上完美运行 但它不会在 Ubuntu Linux 上创建文件 var tempFilename Dat
  • cout 和字符串连接

    我刚刚复习了我的 C 我尝试这样做 include
  • 了解使用 Windows 本机 WPF 客户端进行 ADFS 登录

    我已经阅读了大量有关 ADFS 与 NodeJS Angular 或其他前端 Web 框架集成以及一般流程如何工作的文献 并通过 Auth0 Angular 起始代码构建了概念证明 但我不明白如何这可以与本机 WPF Windows 应用程
  • 跨多个域的 ASP.NET 会话

    是否有合适的 NET 解决方案来在多个域上提供持久服务器会话 即 如果该网站的用户在 www site1 com 下登录 他们也将在 www site2 com 下登录 安全是我们正在开发的程序的一个问题 Thanks 它是否需要在会话中
  • 每个数据库多个/单个 *.edmx 文件

    我有一个通过 ADO net 数据服务与数据库交互的项目 数据库很大 近 150 个具有依赖关系的表 该项目几年前开始 当时使用的是数据集 现在我们正在转向实体模型关系 由于我们添加了更多需要使用的表 该模型正在不断增长 这是管理这一切的正
  • 您是否将信息添加到每个 .hpp/.cpp 文件的顶部? [关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 创建新的 C 头文件 源文件时 您会在顶部添加哪些信息 例如 您是否添加日期 您的姓名 文件描述等 您是否使用结构化格式来存储此信息 e g F
  • QFileDialog::getSaveFileName 和默认的 selectedFilter

    我有 getSaveFileName 和一些过滤器 我希望当用户打开 保存 对话框时选择其中之一 Qt 文档说明如下 可以通过将 selectedFilter 设置为所需的值来选择默认过滤器 我尝试以下变体 QString selFilte

随机推荐

  • 微信小程序 嵌入页面的滚动选择器 picker-view picker-view-column 组件

    完整微信小程序 Java后端 技术贴目录清单页面 必看 嵌入页面的滚动选择器 其中只可放置 picker view column组件 其它节点不会显示 属性 类型 默认值 必填 说明 最低版本 value Array 否 数组中的数字依次表
  • 软工期末( 测试方法)

    白盒测试 白盒测试又称结构测试 透明盒测试 逻辑驱动测试或基于代码的测试 白盒测试是一种测试用例设计方法 盒子指的是被测试的软件 白盒指的是盒子是可视的 即清楚盒子内部的东西以及里面是如何运作的 白盒 法全面了解程序内部逻辑结构 对所有逻辑
  • C#常用的加密算法:MD5、Base64、SHA1、SHA256、HmacSHA256、DES、AES、RSA

    简介 本文主vb net教程要讲c 教程解一下C 常用的python基础教程那些加密java基础教程算法 包括MD5 Base64 SHA1 SHA256 HmacSHA256 DES AES RSA加密sql教程等 有需要的朋友可以参考下
  • Java写入txt文件内容

    Java写入数据进txt文件 需求 多条数据追加进文件 且需要处理中文编码问题 以下代码只能处理向文件添加数据的功能 但是会覆盖掉之前的数据 import java io File import java io FileOutputStre
  • 7-3 一维世界的纷争 (C++) 简单详细

    代码简单 难的点在于读懂题目 帝国A和帝国B统治着一维世界 帝国A的首都位于X 帝国B的首都位于Y 100 X
  • Linux中系统进程的详细管理

    一 什么是进程 进程就是系统未完成并且正在进行的工作 二 查看进程 1 图形方式查看 gnome system monitor 进程状态 R Running 该程序正在运行 S Sleep 改程序目前正在睡眠状态 idle 但可以被唤醒 s
  • VRTK4 入门指南

    VRTK4 说明文档 VRTK Farm Yard 示例 Virtual Reality Toolkit 要求使用 Unity 2020 3 24f1 Beta 免责声明 简介 入门 下载项目 在 Unity 中打开下载的项目 使用 Uni
  • Disruptor(一)Disruptor概念和RingBuffer数据结构

    Disruptor是LMAX公司开源的一个高效的内存无锁队列 谈到并发程序设计 有几个概念是避免不了的 1 锁 锁是用来做并发最简单的方式 当然其代价也是最高的 内核态的锁的时候需要操作系统进行一次上下文切换 等待锁的线程会被挂起直至锁释放
  • Unity 3D控制角色运动的方法

    一 transform Translate 方法 1 Input GetKey KeyCode 按键 if Input GetKey KeyCode W transform Translate Vector3 forward Time de
  • Ubuntu 18.04 16.04 设置输入法切换方法 中文输入法

    新装的unbunu 18 04 16 04 也可以 默认应该有中文输入法 但是应该不太好用 我们安装一个搜狗输入法 下面的shell代码实现了更新软件 检查语言支持 然后下载搜狗输入法 并且安装 然后把fcitx设置为默认输入 然后重启系统
  • 谈一谈关于NLP的落地场景和商业价值

    欢迎大家关注微信公众号 baihuaML 白话机器学习 在这里 我们一起分享AI的故事 您可以在后台留言 关于机器学习 深度学习的问题 我们会选择其中的优质问题进行回答 本期的问题 你好 请问下nlp在现在的市场主要应用在哪些方面 什么是N
  • java调用onnx模型_微软宣布开源ONNX.js,可在浏览器上运行ONNX模型

    ONNX js是一个Java库 用于在浏览器和Node js上运行ONNX模型 ONNX js采用了WebAssembly和WebGL技术 为CPU和GPU提供优化的ONNX模型推理runtime 为何选择ONNX模型 在开放式神经网络交换
  • U盘分配单元大小建议设置多少?

    在对U盘进行格式化的时候 需要先进行单元分配 合理单元分配不仅可以提高U盘的读写速度还不会浪费储存空间 下面就来看看U盘分配单元大小设多少最佳的教程 具体解析如下 1 首先来说U盘的默认格式化的方式都是FAT32的 FAT32不支持大于4G
  • fatal error: Python.h: No such file or directory解决办法

    sudo apt get install scikit image 在安装scikit image时出现如下错误 Installing collected packages subprocess32 python dateutil back
  • Ubuntu连接不了网络的解决方法

    问题描述 突然发现Ubuntu连不上网络 右上角也没有网络图标 解决方案1 解决步骤 步骤一 sudo vim etc NetworkManager NetworkManager conf 1 将其中的managed false 改为 ma
  • python在终端输出不同颜色的打印,自定义日志和自定义log输出级别

    实现过程 终端的字符颜色是用转义序列控制的 是文本模式下的系统显示功能 和具体的语言无关 转义序列是以ESC开头 即用 033来完成 ESC的ASCII码用十进制表示是27 用八进制表示就是033 书写格式 开头部分 033 显示方式 前景
  • 关于对【Lambda表达式_java】的理解与简述

    版权声明 未经博主同意 谢绝转载 请尊重原创 博主保留追究权 https blog csdn net m0 69908381 article details 130522535 出自 进步 于辰的博客 坦白说 在我学会如何使用Lambda表
  • python获取变量名

    一 使用locals 方法 hello 123 loc locals def get variable name variable for k v in loc items if loc k variable return k print
  • lightGBM 回归模型代码

    lightGBM 回归模型代码 文章目录 lightGBM 回归模型代码 lightGBM K折验证效果 模型保存与调用 个人认为 K 折交叉验证是通过 K 次平均结果 用来评价测试模型或者该组参数的效果好坏 通过 K折交叉验证之后找出最优
  • 14.QueuedConnection和BlockingQueuedConnection连接方式源码分析

    QT信号槽直连时的时序和信号槽的连接方式已经在前面的文章中分析过了 见https blog csdn net Master Cui article details 109011425和https blog csdn net Master C