Skip to content

fix: add thread safety for pinyin dictionary initialization#556

Merged
deepin-bot[bot] merged 1 commit intolinuxdeepin:masterfrom
caixr23:master
Apr 21, 2026
Merged

fix: add thread safety for pinyin dictionary initialization#556
deepin-bot[bot] merged 1 commit intolinuxdeepin:masterfrom
caixr23:master

Conversation

@caixr23
Copy link
Copy Markdown
Contributor

@caixr23 caixr23 commented Apr 21, 2026

Add mutex protection to prevent race condition in multi-threaded access to the static pinyin dictionary. The InitDict() function was not thread-safe when called concurrently from multiple threads, which could lead to double initialization or data corruption.

  1. Add QMutex and QMutexLocker includes
  2. Declare static dictMutex to protect the dictionary
  3. Lock mutex at the beginning of InitDict() to ensure exclusive access
  4. Prevent potential crash when multiple threads simultaneously trigger dictionary loading

Influence:

  1. Test concurrent pinyin conversion from multiple threads
  2. Verify no crash occurs during high-frequency multi-threaded access
  3. Test dictionary initialization under thread contention
  4. Verify performance impact of mutex locking in single-threaded scenarios
  5. Test edge cases where InitDict is called simultaneously from different threads

fix: 添加拼音字典初始化的线程安全保护

添加互斥锁保护以防止多线程访问静态拼音字典时的竞态条件。InitDict()
数在被多线程并发调用时不是线程安全的,可能导致重复初始化或数据损坏。

  1. 添加 QMutexQMutexLocker 头文件包含
  2. 声明静态 dictMutex 用于保护字典
  3. InitDict() 开头加锁以确保独占访问
  4. 防止多线程同时触发字典加载时可能导致的崩溃

Influence:

  1. 测试多线程并发进行拼音转换的场景
  2. 验证高频多线程访问时无崩溃发生
  3. 测试线程竞争条件下的字典初始化
  4. 验证单线程场景下互斥锁锁定的性能影响
  5. 测试不同线程同时调用 InitDict 的边界情况

@caixr23
Copy link
Copy Markdown
Contributor Author

caixr23 commented Apr 21, 2026

  • 崩溃堆栈:

[Current thread is 1 (Thread 0x7f3afe04f6c0 (LWP 37684))]
(gdb) bt
#0 __pthread_kill_implementation (threadid=, signo=signo@entry=6, no_tid=no_tid@entry=0) at ./nptl/pthread_kill.c:44
#1 0x00007f3b5d71f19f in __pthread_kill_internal (signo=6, threadid=) at ./nptl/pthread_kill.c:78
#2 0x00007f3b5d6d1282 in __GI_raise (sig=sig@entry=6) at ../sysdeps/posix/raise.c:26
#3 0x00007f3b5d6ba4f2 in __GI_abort () at ./stdlib/abort.c:79
#4 0x00007f3b5d6bb35b in __libc_message (fmt=fmt@entry=0x7f3b5d82f7c7 "%s\n") at ../sysdeps/posix/libc_fatal.c:152
#5 0x00007f3b5d728b25 in malloc_printerr (str=str@entry=0x7f3b5d832708 "double free or corruption (!prev)") at ./malloc/malloc.c:5768
#6 0x00007f3b5d72aa6c in _int_free_merge_chunk (av=av@entry=0x7f3b5d86aac0 <main_arena>, p=0x55cfa2d77570, size=1552)
at ./malloc/malloc.c:4672
#7 0x00007f3b5d72ad71 in _int_free (av=0x7f3b5d86aac0 <main_arena>, p=, have_lock=, have_lock@entry=0)
at ./malloc/malloc.c:4639
#8 0x00007f3b5d72d46f in __GI___libc_free (mem=) at ./malloc/malloc.c:3391
#9 0x00007f3b5f6d28c8 in QHashPrivate::Span<QHashPrivate::Node<unsigned int, QString> >::addStorage (this=0x55cfa3c68f38)
at /usr/include/x86_64-linux-gnu/qt6/QtCore/qhash.h:410
#10 QHashPrivate::Span<QHashPrivate::Node<unsigned int, QString> >::insert (i=53, this=0x55cfa3c68f38)
at /usr/include/x86_64-linux-gnu/qt6/QtCore/qhash.h:284
#11 QHashPrivate::Data<QHashPrivate::Node<unsigned int, QString> >::Bucket::insert (this=)
at /usr/include/x86_64-linux-gnu/qt6/QtCore/qhash.h:512
#12 QHashPrivate::Data<QHashPrivate::Node<unsigned int, QString> >::findOrInsert (this=0x55cfa3c5b7b0,
key=@0x7f3afe04ddc0: 36224) at /usr/include/x86_64-linux-gnu/qt6/QtCore/qhash.h:731
#13 0x00007f3b5f6d0ceb in QHash<unsigned int, QString>::emplace_helper<QString const&> (this=0x7f3b5f76f560 Dtk::Core::dict,
key=@0x7f3afe04ddc0: 36224) at /usr/include/x86_64-linux-gnu/qt6/QtCore/qhash.h:1372
#14 QHash<unsigned int, QString>::emplace<QString const&> (key=@0x7f3afe04ddc0: 36224, this=0x7f3b5f76f560 Dtk::Core::dict)
at /usr/include/x86_64-linux-gnu/qt6/QtCore/qhash.h:1353
#15 QHash<unsigned int, QString>::emplace<QString const&> (key=, this=0x7f3b5f76f560 Dtk::Core::dict)
at /usr/include/x86_64-linux-gnu/qt6/QtCore/qhash.h:1344
#16 QHash<unsigned int, QString>::insert (key=, value=..., this=0x7f3b5f76f560 Dtk::Core::dict)
at /usr/include/x86_64-linux-gnu/qt6/QtCore/qhash.h:1322
#17 Dtk::Core::InitDict () at ./src/util/dpinyin.cpp:44
#18 Dtk::Core::pinyin (words=..., ts=ts@entry=Dtk::Core::TS_NoneTone, ok=ok@entry=0x0) at ./src/util/dpinyin.cpp:190
#19 0x00007f3b2c6059a8 in DCC_NAMESPACE::AppsSourceModel::data (this=, index=..., role=)
at ./src/plugin-notification/operation/appssourcemodel.cpp:84
#20 0x00007f3b2c6052a8 in DCC_NAMESPACE::AppsListModel::lessThan (this=0x7f3af00b8e40, source_left=..., source_right=...)
at ./src/plugin-notification/operation/appslistmodel.cpp:16
#21 0x00007f3b5dde7cc5 in ?? () from /lib/x86_64-linux-gnu/libQt6Core.so.6
#22 0x00007f3b5dde999a in ?? () from /lib/x86_64-linux-gnu/libQt6Core.so.6
#23 0x00007f3b5ddec913 in ?? () from /lib/x86_64-linux-gnu/libQt6Core.so.6
#24 0x00007f3b5dc5b46c in ?? () from /lib/x86_64-linux-gnu/libQt6Core.so.6
#25 0x00007f3b5ddc3f22 in QAbstractItemModel::rowsInserted(QModelIndex const&, int, int, QAbstractItemModel::QPrivateSignal) ()
from /lib/x86_64-linux-gnu/libQt6Core.so.6
#26 0x00007f3b5ddbc896 in QAbstractItemModel::endInsertRows() () from /lib/x86_64-linux-gnu/libQt6Core.so.6
#27 0x00007f3b2c606b9a in DCC_NAMESPACE::AppsSourceModel::appAdded (this=, item=)
at ./src/plugin-notification/operation/appssourcemodel.cpp:159
#28 0x00007f3b2c609bcf in operator() (__closure=__closure@entry=0x7f3afe04e660, appId=...)
--Type for more, q to quit, c to continue without paging--c
at ./src/plugin-notification/operation/notificationmodel.cpp:41
#29 0x00007f3b2c609eab in DCC_NAMESPACE::NotificationModel::NotificationModel (this=0x7f3af0002870, parent=)
at ./src/plugin-notification/operation/notificationmodel.cpp:45
#30 0x00007f3b2c60a0b5 in (anonymous namespace)::NotificationModelDccFactory::create (this=, parent=0x0)
at ./src/plugin-notification/operation/notificationmodel.cpp:81
#31 0x000055cf72e21ac5 in dccV25::LoadPluginTask::doLoadSo (this=this@entry=0x7f3b40012860)
at ./src/dde-control-center/pluginmanager.cpp:168
#32 0x000055cf72e21c52 in dccV25::LoadPluginTask::run (this=0x7f3b40012860) at ./src/dde-control-center/pluginmanager.cpp:124
#33 0x00007f3b5dd4fedd in ?? () from /lib/x86_64-linux-gnu/libQt6Core.so.6
#34 0x00007f3b5dd4a3e4 in ?? () from /lib/x86_64-linux-gnu/libQt6Core.so.6
#35 0x00007f3b5d71d3ed in start_thread (arg=) at ./nptl/pthread_create.c:444
#36 0x00007f3b5d79ee68 in clone3 () at ../sysdeps/unix/sysv/linux/x86_64/clone3.S:78

  • 主线程:

(gdb) thread 3
[Switching to thread 3 (Thread 0x7f3b58a50480 (LWP 37642))]
#0 futex_wait (private=0, expected=2, futex_word=0x7f3b5d86aac0 <main_arena>) at ../sysdeps/nptl/futex-internal.h:146
warning: 146 ../sysdeps/nptl/futex-internal.h: 没有那个文件或目录
(gdb) bt
#0 futex_wait (private=0, expected=2, futex_word=0x7f3b5d86aac0 <main_arena>) at ../sysdeps/nptl/futex-internal.h:146
#1 __GI___lll_lock_wait_private (futex=futex@entry=0x7f3b5d86aac0 <main_arena>) at ./nptl/lowlevellock.c:41
#2 0x00007f3b5d72cff8 in __GI___libc_malloc (bytes=20) at ./malloc/malloc.c:3327
#3 0x00007f3b5dce8918 in QArrayData::allocate2(QArrayData**, long long, QArrayData::AllocationOption) () from /lib/x86_64-linux-gnu/libQt6Core.so.6
#4 0x00007f3b5dcbbfa1 in QString::QString(long long, Qt::Initialization) () from /lib/x86_64-linux-gnu/libQt6Core.so.6
#5 0x00007f3b5dcc70a8 in QString::fromUtf8(QByteArrayView) () from /lib/x86_64-linux-gnu/libQt6Core.so.6
#6 0x00007f3b5f6cf5e2 in QString::QString (ch=0x7f3b5f71c360 "#", this=0x7ffeb36974b0) at /usr/include/x86_64-linux-gnu/qt6/QtCore/qbytearrayview.h:138
#7 Dtk::Core::InitDict () at ./src/util/dpinyin.cpp:39
#8 Dtk::Core::pinyin (words=..., ts=ts@entry=Dtk::Core::TS_NoneTone, ok=ok@entry=0x7ffeb369753f) at ./src/util/dpinyin.cpp:190
#9 0x000055cf72e24a42 in dccV25::SearchSourceModel::addObject (this=this@entry=0x7f3b500153f0, obj=obj@entry=0x7f3b404af3f0, text=..., url=...)
at ./src/dde-control-center/searchmodel.cpp:135
#10 0x000055cf72e26388 in dccV25::SearchSourceModel::addSearchData (this=0x7f3b500153f0, obj=0x7f3b404af3f0, text=..., url=...)
at ./src/dde-control-center/searchmodel.cpp:90
#11 0x000055cf72e2665b in dccV25::SearchModel::addSearchData (this=, obj=, text=..., url=...)
at ./src/dde-control-center/searchmodel.cpp:273
#12 0x000055cf72e0dc5c in dccV25::DccManager::onObjectAdded (this=0x55cfa258f590, obj=0x7f3b404af3f0) at ./src/dde-control-center/dccmanager.cpp:1000
#13 0x00007f3b5dc5b46c in ?? () from /lib/x86_64-linux-gnu/libQt6Core.so.6
#14 0x000055cf72e2a4b6 in dccV25::DccObject::childAdded (this=, _t1=, _t1@entry=0x7f3b404af3f0)
at ./obj-x86_64-linux-gnu/src/dde-control-center/plugin/dde-control-center-lib_autogen/EWIEGA46WW/moc_dccobject.cpp:837
#15 0x000055cf72e37aa6 in dccV25::DccObject::Private::addChild (this=0x55cfa2569ed0, child=child@entry=0x7f3b404af3f0, updateParent=updateParent@entry=true)
at ./src/dde-control-center/plugin/dccobject.cpp:119
#16 0x000055cf72e10525 in dccV25::DccManager::addObjectToParent (this=this@entry=0x55cfa258f590, obj=obj@entry=0x7f3b404af3f0)
at ./src/dde-control-center/dccmanager.cpp:1057
#17 0x000055cf72e11207 in dccV25::DccManager::addObject (this=, obj=) at ./src/dde-control-center/dccmanager.cpp:216
#18 0x00007f3b5dc5b46c in ?? () from /lib/x86_64-linux-gnu/libQt6Core.so.6
#19 0x00007f3b5e2d6ad2 in QQmlComponent::statusChanged(QQmlComponent::Status) () from /lib/x86_64-linux-gnu/libQt6Qml.so.6
#20 0x00007f3b5e2d85bb in QQmlComponentPrivate::typeDataReady(QQmlTypeData*) () from /lib/x86_64-linux-gnu/libQt6Qml.so.6
#21 0x00007f3b5e39498c in ?? () from /lib/x86_64-linux-gnu/libQt6Qml.so.6
#22 0x00007f3b5e3b5cf9 in ?? () from /lib/x86_64-linux-gnu/libQt6Qml.so.6
#23 0x00007f3b5e2af129 in ?? () from /lib/x86_64-linux-gnu/libQt6Qml.so.6
#24 0x00007f3b5dc0eeb8 in QCoreApplication::notifyInternal2(QObject*, QEvent*) () from /lib/x86_64-linux-gnu/libQt6Core.so.6
#25 0x00007f3b5dc16d77 in QCoreApplicationPrivate::sendPostedEvents(QObject*, int, QThreadData*) () from /lib/x86_64-linux-gnu/libQt6Core.so.6
#26 0x00007f3b5de06507 in ?? () from /lib/x86_64-linux-gnu/libQt6Core.so.6
#27 0x00007f3b5d2ae869 in ?? () from /lib/x86_64-linux-gnu/libglib-2.0.so.0
#28 0x00007f3b5d2b0967 in ?? () from /lib/x86_64-linux-gnu/libglib-2.0.so.0
#29 0x00007f3b5d2b0f70 in g_main_context_iteration () from /lib/x86_64-linux-gnu/libglib-2.0.so.0
#30 0x00007f3b5de03d50 in QEventDispatcherGlib::processEvents(QFlagsQEventLoop::ProcessEventsFlag) () from /lib/x86_64-linux-gnu/libQt6Core.so.6
#31 0x00007f3b5dc17c2a in QEventLoop::exec(QFlagsQEventLoop::ProcessEventsFlag) () from /lib/x86_64-linux-gnu/libQt6Core.so.6
#32 0x00007f3b5dc11ce8 in QCoreApplication::exec() () from /lib/x86_64-linux-gnu/libQt6Core.so.6
#33 0x000055cf72e00614 in main (argc=, argv=) at ./src/dde-control-center/main.cpp:220

@caixr23 caixr23 force-pushed the master branch 3 times, most recently from 91766f1 to 73cb085 Compare April 21, 2026 05:50
Add mutex protection to prevent race condition in multi-threaded access
to the static pinyin dictionary. The `InitDict()` function was not
thread-safe when called concurrently from multiple threads, which could
lead to double initialization or data corruption.

1. Add `QMutex` and `QMutexLocker` includes
2. Declare static `dictMutex` to protect the dictionary
3. Lock mutex at the beginning of `InitDict()` to ensure exclusive
access
4. Prevent potential crash when multiple threads simultaneously trigger
dictionary loading

Influence:
1. Test concurrent pinyin conversion from multiple threads
2. Verify no crash occurs during high-frequency multi-threaded access
3. Test dictionary initialization under thread contention
4. Verify performance impact of mutex locking in single-threaded
scenarios
5. Test edge cases where InitDict is called simultaneously from
different threads

fix: 添加拼音字典初始化的线程安全保护

添加互斥锁保护以防止多线程访问静态拼音字典时的竞态条件。`InitDict()` 函
数在被多线程并发调用时不是线程安全的,可能导致重复初始化或数据损坏。

1. 添加 `QMutex` 和 `QMutexLocker` 头文件包含
2. 声明静态 `dictMutex` 用于保护字典
3. 在 `InitDict()` 开头加锁以确保独占访问
4. 防止多线程同时触发字典加载时可能导致的崩溃

Influence:
1. 测试多线程并发进行拼音转换的场景
2. 验证高频多线程访问时无崩溃发生
3. 测试线程竞争条件下的字典初始化
4. 验证单线程场景下互斥锁锁定的性能影响
5. 测试不同线程同时调用 InitDict 的边界情况
@deepin-ci-robot
Copy link
Copy Markdown
Contributor

deepin pr auto review

这段代码的修改主要涉及到了版权年份更新、头文件包含顺序调整、以及字典初始化逻辑的线程安全改进。下面是对这段代码的详细审查和改进建议:

1. 语法逻辑审查

优点:

  • 使用了 std::call_oncestd::once_flag 实现了线程安全的单次初始化,这是一个很好的改进,解决了多线程环境下可能出现的竞态条件问题。
  • 将初始化逻辑拆分为 initDictImplInitDict,职责分离清晰。

建议:

  • initDictImpl 中,dict.reserve(25333) 硬编码了字典大小,建议改为根据实际文件内容动态计算或使用常量定义,提高代码可维护性。
  • initToneTable 函数中的 if (toneTable.size() > 0) 可以改为 if (!toneTable.isEmpty()),更符合 Qt 的编码风格。

2. 代码质量审查

优点:

  • 代码结构清晰,函数命名符合规范。
  • 使用了 Qt 的容器类(如 QHashQMap)和文件操作类(QFileQTextStream),符合 Qt 框架的使用习惯。

建议:

  • 头文件包含顺序可以进一步优化,通常建议按照以下顺序:
    1. 对应的头文件(如 dpinyin.h
    2. C 标准库头文件
    3. C++ 标准库头文件
    4. Qt 库头文件
    5. 项目内部头文件
  • kDictFile 可以改为 constexprinline 变量(C++17),避免全局静态变量。
  • initDictImpl 中的错误处理可以更完善,例如在文件打开失败时记录日志或抛出异常。

3. 代码性能审查

优点:

  • 使用了 dict.reserve(25333) 预分配哈希表空间,减少了动态扩容的开销。
  • 使用了 QHash 而不是 QMap,查找效率更高(O(1) vs O(log n))。

建议:

  • 如果字典内容在程序运行期间不会改变,可以考虑使用 conststatic const 修饰 dict,避免意外修改。
  • initToneTable 中的 toneTable 是引用传递,但函数内部没有修改它的逻辑,可以改为 const 引用。

4. 代码安全审查

优点:

  • 使用了 std::call_once 保证了线程安全。
  • 文件操作使用了 QFileQTextStream,避免了手动管理文件句柄的内存泄漏风险。

建议:

  • kDictFile 是一个硬编码的路径,如果文件路径可能被篡改或不存在,建议增加路径有效性检查。
  • initDictImpl 中没有对文件内容进行校验,如果文件格式错误可能导致程序崩溃。建议增加格式检查或异常处理。
  • dict 是全局静态变量,可能被其他模块意外修改,建议封装为类或使用命名空间限制访问权限。

5. 其他改进建议

  • 版权年份:版权年份更新为 2026 是合理的,但建议使用 2023 - 2026 而不是直接替换为 2026,保留历史信息。
  • 代码注释:建议为关键函数(如 InitDicttoned)添加注释,说明其用途和实现细节。
  • 测试覆盖:建议为字典初始化和拼音转换逻辑添加单元测试,确保功能正确性。

改进后的代码示例

// SPDX-FileCopyrightText: 2019 - 2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later

#include "dpinyin.h"

#include <QFile>
#include <QMap>
#include <QSet>
#include <QTextStream>
#include <QDebug>

#include <mutex>

DCORE_BEGIN_NAMESPACE

constexpr char kDictFile[] = ":/dpinyin/resources/dpinyin.dict";
static QHash<uint, QString> dict = {};
static std::once_flag dictInitFlag;

static void initDictImpl()
{
    dict.reserve(25333); // 建议根据实际字典大小调整

    QFile file(kDictFile);
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        qWarning() << "Failed to open dictionary file:" << kDictFile;
        return;
    }

    QTextStream in(&file);
    while (!in.atEnd()) {
        QString line = in.readLine();
        QStringList parts = line.split(' ');
        if (parts.size() != 2) {
            qWarning() << "Invalid dictionary line:" << line;
            continue;
        }

        bool ok;
        uint key = parts[0].toUInt(&ok, 16);
        if (!ok) {
            qWarning() << "Invalid hex key in dictionary:" << parts[0];
            continue;
        }

        dict[key] = parts[1];
    }
}

static void InitDict()
{
    std::call_once(dictInitFlag, initDictImpl);
}

static void initToneTable(QMap<QChar, QString> &toneTable)
{
    if (!toneTable.isEmpty()) {
        return;
    }

    toneTable.insert('ā', "a1");
    toneTable.insert('á', "a2");
    toneTable.insert('ǎ', "a3");
    toneTable.insert('à', "a4");
    // 其他拼音字符...
}

static QString toned(const QString &str, ToneStyle ts)
{
    static QMap<QChar, QString> toneTable;
    initToneTable(toneTable);

    QString newStr = str;
    QString toneNum = "";

    // First pass: find tone number and remove tone marks
    for (QChar c : str) {
        if (toneTable.contains(c)) {
            newStr.replace(c, toneTable[c][0]);
            toneNum += toneTable[c][1];

            if (ts == TS_ToneNone) {
                newStr = str; // 重置为原始字符串
                break;
            }
        }
    }

    // For TS_ToneNum, append tone number at the end
    if (ts == TS_ToneNum && !toneNum.isEmpty()) {
        newStr += toneNum;
    }

    return newStr;
}

总结

这段代码的改进方向主要集中在线程安全、错误处理和代码可维护性上。通过使用 std::call_once 实现了线程安全的初始化,但可以进一步优化错误处理和代码结构。建议添加更多的日志记录和异常处理,以提高代码的健壮性。

@deepin-ci-robot
Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: 18202781743, caixr23

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@caixr23
Copy link
Copy Markdown
Contributor Author

caixr23 commented Apr 21, 2026

/forcemerge

@deepin-bot
Copy link
Copy Markdown
Contributor

deepin-bot Bot commented Apr 21, 2026

This pr force merged! (status: blocked)

@deepin-bot deepin-bot Bot merged commit dab46c0 into linuxdeepin:master Apr 21, 2026
17 of 20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants