目錄
一,事件
1.1 關于事件
1.2 處理事件
1.3 處理鼠標事件
1.3.1 點擊事件
1.3.2 釋放事件
1.3.3? 雙擊事件
1.3.4 滾輪事件
1.3.5?注意事項
1.4 處理鍵盤事件
1.5 定時器事件
1.6 窗口移動和大小改變事件
二,文件操作
2.1 文件操作概述
2.2 QFile 介紹
2.3 QFile 使用
2.4 QFileInfo 使用
三,Qt多線程
3.1 介紹
3.2 多線程版倒計時
3.3 鎖
3.4 條件變量和信號量
雖然Qt是跨平臺的 C++ 開發框架,但是Qt 的很多能力其實是操作系統提供的,只不過 Qt 封裝了系統的 API
一,事件
1.1 關于事件
用戶進行的各種操作,就可能會產生信號,可以指定槽函數,當信號觸發時,就能夠自動執行到對應的槽函數
同時,用戶的各種操作,也會產生“事件”,事件的概念和信號非常相似,同樣也可以給事件關聯上一些函數,當事件觸發時,能夠執行到對應代碼
事件與信號槽的關系:
- 事件本身是操作系統提供的機制,Qt 也就是將其進行了封裝
- 但是事件對應的代碼編寫起來不是很方便,所以Qt 對事件機制進行進一步的封裝,就有了信號槽
- 所以信號槽是對于事件的進一步封裝,事件是信號槽的底層機制
實際開發中,絕大部分和用戶的交互都是通過“信號槽”來完成,但在有些特殊情況下,信號槽可能無法滿足需求,所以此時就需要重寫事件處理函數的形式,來手動處理事件的響應邏輯
常見的 Qt 事件如下:
?常見事件描述:
名稱 | 描述 |
---|---|
鼠標事件 | 鼠標左鍵、鼠標右鍵、鼠標滾輪,鼠標的移動,鼠標按鍵的按下和松開 |
鍵盤事件 | 按鍵類型、按鍵按下、按鍵松開 |
定時器事件 | 定時時間到達 |
進入離開事件 | 鼠標的進入和離開 |
滾輪事件 | 鼠標滾輪滾動 |
繪屏事件 | 重繪屏幕的某些部分 |
顯示隱藏事件 | 窗口的顯示和隱藏 |
移動事件 | 窗口位置的變化 |
窗口事件 | 是否為當前窗口 |
大小改變事件 | 窗口大小改變 |
焦點事件 | 鍵盤焦點移動 |
拖拽事件 | 用鼠標進行拖拽 |
1.2 處理事件
所謂處理事件,就是將事件和一段代碼關聯起來,當事件觸發時,就能執行這段代碼
之前我們通過 connect 將事件和槽關聯,但是要想關聯事件,需要重寫某個事件處理函數
下面我們演示一下鼠標進入和鼠標離開事件,假設 有一個按鈕,當鼠標移到上面時就會觸發鼠標進入事件,移開時會觸發離開事件,需要重寫的虛函數如下:
?我們先創建一個繼承 QWidget 的項目,我們可以在界面上放一個 label,當鼠標移動到 label 里時,顯示一些文字,離開 label 時顯示另一些文字:
然后我們創建一個 QLabel 的子類,然后在這個子類里重寫 enterEvent 和 leaveEvent:
然后修改下構造函數:
然后就是重寫兩個虛函數了,下面是 label.cpp 的內容:
#include "label.h"Label::Label(QWidget* parent) : QLabel(parent)
{}void Label::enterEvent(QEvent *event)
{this->setText("鼠標進來了");
}void Label::leaveEvent(QEvent *event)
{this->setText("鼠標出去了");
}
但是此時我們執行后,我們的 label 并沒有什么變化,因為,我們在ui界面通過拖拽方式創建的 Label,還是 QLabel 類型,所以我們需要提升 label 的類型,如下:
?
然后就可以處理事件了,效果如下:
1.3 處理鼠標事件
1.3.1 點擊事件
我們下面演示一下通過事件獲取鼠標點擊的位置
以1.2 中的代碼為例進行擴展,先把label進行擴大:
需要重寫的函數如下:
label.cpp 代碼如下:
#include "label.h"
#include <QMouseEvent>
#include <QDebug>Label::Label(QWidget* parent) : QLabel(parent)
{}void Label::mousePressEvent(QMouseEvent *event)
{//當前 event 就包含了鼠標的位置qDebug() << "控件里位置:" << event->x() << ", " << event->y(); //原點是控件左上角而不是窗口左上角qDebug() << "屏幕上位置:" << event->globalX() << ", " << event->globalY(); //這個是相對于 “整個屏幕” 左上角為原點的位置//這個函數其實按下左鍵、右鍵、滾輪都能觸發有些鼠標還帶有前進后退鍵,也可以觸發if(event->button() == Qt::LeftButton) qDebug() << "按下左鍵";else if(event->button() == Qt::RightButton) qDebug() << "按下右鍵";else qDebug() << "按下其它鍵";
}
效果如下:
1.3.2 釋放事件
要重寫的虛函數為:
?文件還是前面的 label.cpp ,重寫的事件函數如下:
void Label::mouseReleaseEvent(QMouseEvent *event)
{if(event->button() == Qt::LeftButton) qDebug() << "左鍵釋放";else if(event->button() == Qt::RightButton) qDebug() << "右鍵釋放";else qDebug() << "其它鍵釋放";
}
效果和上面類似:
1.3.3? 雙擊事件
要重寫的槽函數為:
重寫的函數如下:
void Label::mouseDoubleClickEvent(QMouseEvent *event)
{if(event->button() == Qt::LeftButton) qDebug() << "左鍵雙擊";else if(event->button() == Qt::RightButton) qDebug() << "右鍵雙擊";else qDebug() << "其它鍵雙擊";
}
只有當第二次按下時,才能識別為“雙擊”,所以一次雙擊按順序會觸發四個事件:按下,釋放,雙擊,釋放
1.3.4 滾輪事件
需要重寫的虛函數為:
代碼如下:
int total = 0;
void Label::wheelEvent(QWheelEvent *event) //QWheelEvent 是一個專門的鼠標滾輪的類
{total += event->delta();//可以獲取鼠標滾動了多遠qDebug() << total;
}
1.3.5?注意事項
注意一:如果想將上面鼠標的事件從label控件擴展到整個窗口,也只需要在 QWidget 類中重寫對應的虛函數即可
?注意二:關于鼠標移動事件:
- 鼠標移動事件不同于其它的鼠標事件,只要鼠標移動,就會產生巨量的鼠標移動事件,一旦該事件的邏輯比較多,系統就容易卡頓
- 所以 Qt 為了程序的流暢性,鼠標移動時,不會調用 mouseMoveEvent ,除非是顯示告訴 Qt 就要追蹤鼠標位置,需要在構造函數里設置:this->setMouseTracking(true);? 告訴Qt我要追蹤鼠標位置
1.4 處理鍵盤事件
按鍵事件是通過 QKeyEvent 類來實現,我們前面是通過 QShortCut 搭配?QKeySequence 的,先通過 QShortCut 綁定一個快捷鍵,當快捷鍵被按下,會產生一個信號,再通過槽進行代碼邏輯
當然,上面是信號槽機制封裝過的獲取鍵盤按鍵的方式,站在更底層的角度,也可以通過事件獲取到用戶鍵盤按下的情況的,需要重寫的虛函數如下:
重寫的函數如下:
void Label::keyPressEvent(QKeyEvent *event)
{//可以檢測單個按鍵,也可以檢測組合鍵if(event->modifiers() == Qt::ControlModifier) //判斷Ctrl鍵是否被按下{if(event->key() == Qt::Key_A)qDebug() << "Ctrl + A 被按下";}
}
?Qt::KeyboardModifier 中定義了在處理鍵盤事件時對應的修改鍵。在Qt中,鍵盤事件可以與修改鍵 ?起使用,以實現?些復雜的交互操作。KeyboardModifier中修改鍵的具體描述如下:
- Qt::NoModifier:無修改鍵
- Qt::ShiftModifier:Shift 鍵
- Qt::ControlModifier:Ctrl 鍵
- Qt::AltModifier:Alt 鍵
- Qt::MetaModifier:Meta鍵(在Windows上指Windows鍵,在macOS上指Command鍵)
- Qt::KeypadModifier:使用鍵盤上的數字鍵盤進行輸?時,Num Lock鍵處于打開狀態
- Qt::GroupSwitchModifier:用于在輸入法組之間切換
1.5 定時器事件
Qt 中的定時器分為 QTimerEvent 和 QTImer 兩個類:
- QTimerEvent類:用來描述一個定時器事件。在使用時需要通過 startTimer() 函數來開啟一個定時器,需要輸入一個以 ms 為單位的整數作為參數來表明設定的時間,返回值代表這個定時器。當到達指定時時間時,就可以在 timerEvent() 函數中獲取該定時器的編號來進行相關操作
- QTimer類:來實現一個定時器,它提供了更高層次的編程接口,如:可以使用信號和槽,還可以設置只運行一次的定時器
我們在 ui 界面上搞兩個 Label 控件,一個每過1秒讓數字累加一次,一個每過2秒讓數字累加一次:
然后我們就可以重寫 timerEvent函數了,先在 widget.h 里聲明函數:
然后在 widget.cpp 里重寫定時器事件:
#include "widget.h"
#include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);//啟動定時器t1 = startTimer(1000);t2 = startTimer(2000);//此時這個 t1 和 t2 是一個定時器的標識,或者id//類似我們 Linux 里的文件描述符,起到身份標識的作用
}Widget::~Widget()
{delete ui;
}void Widget::timerEvent(QTimerEvent *e)
{//如果一個程序中存在多個定時器(startTimer 創建的定時器),此時每個定時器都會觸發這個 timerEvent 函數if(e->timerId() == t1){static int n1 = 1;ui->label->setText(QString::number(n1++)); //每個一秒加一次}if(e->timerId() == t2){static int n2 = 1;ui->label_2->setText(QString::number(n2++)); //每隔兩秒加一次}
}
?
使用 timerEvent 比 QTimer 復雜得多,不僅要手動管理 timerId,還需要注意多個定時器同時調用這個函數時的區分問題,所以后續開發中,使用 QTimer 即可,我們之前有介紹:QT跨平臺應用程序開發框架(6)—— 常用顯示類控件-CSDN博客
1.6 窗口移動和大小改變事件
- moveEvent:窗口移動時觸發的事件
- resizeEvent:窗口大小改變時觸發的事件
?直接重寫這兩個函數即可:
void Widget::moveEvent(QMoveEvent *event)
{qDebug() << event->pos(); //每次移動都打印窗口的位置,左上角在屏幕上的位置
}void Widget::resizeEvent(QResizeEvent *event)
{qDebug() << event->size(); //每次調整大小都打印目前窗口的大小
}
二,文件操作
C++文件操作:C++——IO流-CSDN博客
C語言文件操作:C語言文件操作-CSDN博客
2.1 文件操作概述
- C語言中,我們通過 fopen 打開文件,通過 fread 和 fwrite 讀寫文件,fclose 關閉文件?
- C++中,我們通過 fstream 打開文件,<< 和 >> 讀寫文件,close 關閉文件
- Linux中,我們也通過原生 API 的 open 打開文件,read 和 write 讀寫文件,close 關閉文件
我們在 Qt 中也可以使用上述幾種方案來讀寫文件(Linux 需要在 Linux 系統上),但是 Qt 自己也提供了一套文件操作的 API,因為 Qt 誕生的很早,那時候 C++ 還沒有“標準化”的概念
所以下面我們都是使用Qt自己提供的這一套文件操作,因為和 QString 等 Qt 內置類進行很好的兼容和配合?
Qt 的文件操作也基本是三個:打開,讀寫,關閉,都是用的 QFile 類來完成操作,主要繼承關系如下圖:
- QFile:用于文件操作和文件數據讀寫的類,使用 QFile 可以讀寫任意格式的件
- QSaveFile:用于安全保存文件的類。使用 QSaveFile 保存文件時,會先把數據寫入一個臨時文件,成功提交后才將數據寫入最終的文件。如果保存過程中出現錯誤,臨時文件里的數據不會被寫入最終文件,這樣就能確保最終文件中不會丟失數據或只寫入了部分數據。在保存比較大的文件或復雜格式的文件時可以使用這個類,例如從網絡上下載文件等
- QTemporaryFile:用于創建臨時文件的類。使用函數 QTemporaryFile::open() 就能創建一個文件名唯一的臨時文件,在 QTemporaryFile 對象被刪除時,臨時文件也被動刪除
- QTcpSocket 和 QUdpSocket:分別實現了TCP和UDP的類
- QSerialPort:是實現了串口通信的類,通過這個類可以實現計算機與串口設備的通信(窗口是一種比較古老的通信方式,一般是在嵌入式系統上)
- QBluetoothSocket:用于藍牙通信的類。手機、平板計算機和筆記本電腦等移動設備都有藍牙通信模塊。通過 QBluetoothSocket 類,就可以編寫藍牙通信程
- QProcess:用于啟動外部程序,并且可以給程序傳遞參數
- QBuffer:以一個 QByteArray 對象作為數據緩沖區,將 QByteArray 對象當作一個I/O設備來讀寫
2.2 QFile 介紹
在 Qt 中,文件的讀寫主要是通過 QFile 類來實現,Qt 中讀寫文件的方法有:
- 打開文件:open
- 讀文件:read,readLine,readAll 等
- 寫文件:write,writeData 等
- 關閉文件:close
更多的操作可以在文檔中查詢關鍵詞 QFile 了解:
①打開:open?
open 有好幾個版本,比如下面兩個:
一個是 FILE*,一個是文件描述符,用起來比較麻煩,所以我們一般用的都是這個:
構造函數中,只需要指定路徑后用 open 直接打開即可,OpenMode 是打開的方式,有讀方式,寫方式,追加寫等方式,要詳細了解直接在文檔里搜索 OpenMode 即可
②讀文件:read,readLine,readAll
在 QIODevice 類里可以找到讀文件相關函數的介紹:
?③寫文件:write
④關閉文件:close
2.3 QFile 使用
我們再次創建 mainwindows 項目,直接通過代碼去構造界面
先是 mainwindow.h ,在里面聲明函數:
然后是 mainwindow.cpp 的代碼:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QPlainTextEdit>
#include <QFileDialog>MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);//獲取到菜單欄QMenuBar* menuBar = this->menuBar();//添加菜單QMenu* m = new QMenu("文件");menuBar->addMenu(m);//添加菜單項QAction* a1 = new QAction("打開");QAction* a2 = new QAction("保存");m->addAction(a1);m->addAction(a2);//指定一個輸入框edit = new QPlainTextEdit();this->setCentralWidget(edit);//把字體放大一些QFont font;font.setPixelSize(20);edit->setFont(font);//連接 QAction 的信號槽connect(a1, &QAction::triggered, this, &MainWindow::handleAction1);connect(a2, &QAction::triggered, this, &MainWindow::handleAction2);
}MainWindow::~MainWindow()
{delete ui;
}void MainWindow::handleAction1()
{//1,先彈出一個“打開文件”的對話框,選擇文件QString path = QFileDialog::getOpenFileName(this);//2,將文件名顯示到狀態欄里(也可以搞一個 label,然后放進去)QStatusBar* s = this->statusBar(); //獲取狀態欄s->showMessage(path);//3,根據文件路徑構建 QFile 對象QFile file(path);bool ret = file.open(QIODevice::ReadOnly); //以只讀方式打開文件if(!ret){s->showMessage("路徑錯誤或文件不存在!");return;}//4,讀取文件QString text = file.readAll();//可以直接用 QString 來接收,因為Qstring 重載了構造函數//可以用 QByteArray,也就是 readAll 的返回值對象來構造 QString 對象//但是這樣需要確保打開的是一個文本文件才行,如果是二進制文件,交給 QString 就不合適了//因為二進制文件沒有限制,圖片,視頻等都可以//文本文件必須得是合法字符(指的是遵循 utf8,gbk等編碼方式)//5,關閉文件file.close();//6,將讀取的內容顯示在輸入框中edit->setPlainText(text);
}void MainWindow::handleAction2()
{//1,先彈出“保存文件”對話框QString path = QFileDialog::getOpenFileName(this);//2,再狀態欄中顯示文件名QStatusBar* s = this->statusBar();s->showMessage(path);//3,根據用戶選擇的路徑,構造 QFile 對象,并打開文件QFile file(path);bool ret = file.open(QFile::WriteOnly); //只寫方式if(!ret){s->showMessage("路徑錯誤或文件不存在!");return;}//4,寫文件const QString& text = edit->toPlainText();file.write(text.toUtf8());//5,關閉文件file.close();
}
這樣就完成了一個簡單的針對文本文件的打開修改和保存的窗口了?
2.4 QFileInfo 使用
對于文件不僅僅只有讀寫,還有例如獲取屬性的一系列操作,如下:
- isDir():檢查該文件是否是目錄?
- isExecutable():檢查該文件是否是可執行文件
- fileName():獲得文件名
- completeBaseName():獲取完整的文件名
- suffix():獲取文件后綴名
- completeSuffix():獲取完整的文件后綴
- size():獲取文件大小
- isFile():判斷是否為文件
- fileTime():獲取文件創建時間、修改時間、最近訪問時間等
?我們可以通過 QFileInfo 獲取到 Qt 的文件的相關屬性,下面我們直接創建一個按鈕,要求是點擊按鈕后,打開文件選擇窗口,選擇好文件后,打印文件的信息,按鈕槽函數如下:
void MainWindow::on_pushButton_clicked()
{//彈出文件對話框,并獲取文件屬性信息QString path = QFileDialog::getOpenFileName(this);QFileInfo f(path);qDebug() << f.fileName();qDebug() << f.suffix();qDebug() << f.path();qDebug() << f.size();qDebug() << f.isFile();qDebug() << f.isDir();
}
三,Qt多線程
3.1 介紹
Qt 多線程概念和 Linux 本質沒有區別,可以參考:
- Linux系統編程——線程基本概念-CSDN博客
- Linux系統編程——線程同步互斥與線程安全-CSDN博客
Linux原生的多線程 API,了解即可,因為使用起來很麻煩,可以看到上面兩篇文章里的多線程代碼很長也很難理解,所以實際開發中很少使用 原生的線程 API
Qt 中的多線程 API,參考了 Java 中線程庫 API 的設計方式?
在 Qt 中,多線程的處理一般通過 QThread類 來實現,它代表一個在應用程序中可以獨立控制的線程,也可以和進程中的其它線程共享數據
總的來說 QThread對象 用于管理程序中的一個線程,常用 API 如下:
API | 說明 |
---|---|
run() | 線程的入口函數
|
start() | 通過調用 run() 開始執行線程
|
currentThread() | 返回?個指向管理當前執行線程的 QThread 的指針 |
isRunning() | 如果線程正在運行則返回 true 否則返回false |
sleep() / msleep() / usleep() | 使線程休眠,單位為秒 / 毫秒 / 微秒 |
wait() | 阻塞線程,直到滿足以下任何?個條件:
|
terminate() | 終止線程的執行。線程可以立即終止,也可以不立即終止,這取決于操作系統的調度策略 在 terminate ()之后使用QThread::wait()來確保。 |
finished() | 當線程結束時會發出該信號,可以通過該信號來實現線程的清理工作 |
3.2 多線程版倒計時
我們之前使用定時器搞過一個倒計時這樣的程序:QT跨平臺應用程序開發框架(6)—— 常用顯示類控件-CSDN博客
咱們也可以通過線程來完成這樣的功能,先創建一個人基于 QWidget 的項目,然后創建QWidget 的子類,在 thread.h 中聲明下 run 函數,這時候就可以在 .cpp 里重寫 run 函數了 :
然后就是編寫線程的run函數邏輯了?
注意,此時不能直接在 run 中修改界面內容,前面說過,由于存在線程安全問題,Qt 規定只有主線程才能對界面控件進行修改?
如下 thread.cpp 的代碼:
#include "thread.h"Thread::Thread()
{}void Thread::run()
{//不能修改界面,但是可以計時,每過一秒鐘,通過信號槽,通知主線程去更新頁面for(int i = 0; i < 10; i++){sleep(1);//發送一個信號去通知主線emit notify();}
}
?創建另一個線程,在新線程中進行計時,每循環一次就是 sleep(1),然后就可以更新界面了
下面是 widget.cpp 的代碼:
#include "widget.h"
#include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);connect(&thread, &Thread::notify, this, &Widget::handle); //將自定義函數綁定 widgetthread.start(); //啟動線程,start() 其實就是去調用 run()
}Widget::~Widget()
{delete ui;
}void Widget::handle() //記得在 .h 中聲明
{//此處修改頁面內容即可int value = ui->lcdNumber->intValue();if(value > 0) value--;ui->lcdNumber->display(value);
}
應用場景:
- 我們在 Linux 系統里學習多線程,主要是站在服務器開發角度來看待的:Linux 多線程使用的目的,是為了充分利用多核 CPU 的計算找資源,因為一些高性能服務器可能有2個甚至更多個 CPU
- 但是對于客戶端來說,用戶的“使用體驗”是很重要的,如果“非常快”的代價是“系統很卡”,就會降低使用體驗,所以客戶端上的程序很少會用多線程把 CPU 資源占完,畢竟用戶的個人電腦手機等不僅僅只運行你一個程序
- 所以客戶端中的多線程,主要用于執行一個耗時的等待 IO 的操作,避免主線程長時間等待 IO 時卡死,比如客戶端 上傳/下載 一個很大的文件,需要長時間傳輸,這時候就可以用線程來執行下載操作而不會導致頁面卡死了
3.3 鎖
談到多線,就不得不提到“線程安全”這個大話題,3.1 介紹 的兩篇文章已經介紹了 線程安全問題的一系列原因后果和解決方法,這里不再贅述
加鎖是解決線程安全的最簡單的辦法,Qt 同樣也提供了對于的鎖 QMutex,來針對系統的鎖進行封裝,主要也提供了兩種方法:lock 和 unclock ,負責加鎖和解鎖,下面來演示一下:
下面是 thread.h 的代碼,主要包括:聲明run函數,添加靜態變量和靜態鎖:
#ifndef THREAD_H
#define THREAD_H#include <QWidget>
#include <QThread>
#include <QMutex>class Thread : public QThread
{Q_OBJECT
public:Thread();void run(); //重要的是重寫父類的run函數//添加一個 static 成員變量,然后讓兩個線程都去修改這個變量static int num;static QMutex mutex;};#endif // THREAD_H
重寫run函數,讓其對 num 進行加加操作:
void Thread::run()
{for(int i = 0; i < 50000; i++){mutex.lock();num++;mutex.unlock();}
}
?然后我們的 主邏輯如下:
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);Thread t1;Thread t2;t1.start();t2.start();//加上等待,讓主線程等待這倆線程執行結束t1.wait();t2.wait();qDebug() << Thread::num;
}
上面只是鎖的最簡單的用法,鎖和動態內存一樣,都需要手動釋放,但是一旦中間有個 if跳過了釋放邏輯,或者直接拋異常了,就會造成死鎖問題
動態內存無法釋放的問題,我們一般用智能指針來搞:C++——C++11智能指針_智能指針c++11-CSDN博客?
- 在C++中,對于鎖的釋放,C++11 引入了 std::lock_guard,相當于是 std::mutex 的智能指針,借助了 RAII 機制
- Qt 也提供了類似的設計,用到的是 QMutexLocker 類,所以下面我們來調一下 run 函數的代碼
void Thread::run()
{for(int i = 0; i < 50000; i++){QMutexLocker locker(&mutex);//mutex.lock();num++;//mutex.unlock();}
}
3.4 條件變量和信號量
關于條件變量,3.1 的第二個鏈接文章的最后一個標題已經介紹過,而關于信號量:Linux系統編程——進程間通信(管道與共享內存)_共享內存 管道-CSDN博客
多個線程之間的調度是無序的,所以我們為了能夠一定程度上干預線程的執行順序,引入了條件變量,Qt 中的條件變量通過 QWaitCondition 來實現,提供了 wait 和 wake,標識等待和喚醒,還有一個 wakeAll ,喚醒所有線程
注意:只有在 mutex.lock() 后才能 wait 等待,因為 wait 函數會先釋放鎖,然后再等待,所以要想釋放鎖,必須先得到鎖?
這兩個和我們 Linux 中的使用方式基本一致,只是 API 不一樣,這里就不演示了?:Linux系統編程——線程同步互斥與線程安全-CSDN博客
API 本身的使用并不麻煩,更重要的是 API 背后的運行邏輯等?