Linux:線程概念與控制

博客封面

??所屬專欄:Linux??

??作者主頁:嶔某??

Linux:線程概念于控制

var code = “d7e241ae-ed4d-475f-aa3d-8d78f873fdca”

概念

在一個程序里的一個執行路線就叫做線程thread。更準確一點:線程是“一個進程內部的控制序列”

一切進程都至少有一個線程

線程在進程內部運行,本質是在進程地址空間運行

Linux系統中,在CPU眼中,看到的PCB都要比傳統的要輕量化

透過進程虛擬地址空間,可以看到進程的大部分資源,將進程資源合理分配給每個執行流,就形成了線程執行流。

image-20250408221044041

分頁式儲存管理

如果沒有虛擬內存和分頁機制,每一個用戶在物理內存上的空間必須是連續的,如下圖

8b127986cf552877b7077b3a82fedaa

因為每一個程序的代碼、數據長度都是不一樣的,按照這樣的映射方式,物理內存會被分成各種離散的大小不同的塊。有些程序會退出,它們占據的物理內存會被回收,一段時間過后,物理內存就變得非常碎片化,不易管理了。

所以我們希望操作系統給用戶的空間是連續的,但是物理內存最好不要連續,那么虛擬內存和分頁就出現了。

image-20250409223428641

把物理內存按照一個固定的長度頁框進行分割,叫做物理頁。每一個頁框包含一個物理頁page。頁的大小就是頁框的大小。32位機器支持4kb的頁,64位機器支持8kb的頁。

  • 頁框是一個儲存區域
  • 頁是一個數據塊,可以存放在任何頁框中,或者磁盤中

有了這種機制,CPU并非直接訪問物理地址,而是通過虛擬地址空間來間接的訪問物理內存地址。所謂的虛擬地址空間,是操作系統位每一個在執行的進程分配的一個邏輯地址,32位機器上其范圍為0-4GB

操作系統通過將虛擬地址空間與物理地址間建立映射關系:頁表,這張表上記錄了每一對頁和頁框的映射關系,能讓CPU間接訪問物理內存地址。

其思想就是**將虛擬內存下的邏輯地址空間分為若干的頁,將物理內存空間分為若干頁框,通過頁表將連續的虛擬內存,映射到若干個不連續的物理內存頁。**這樣就解決了使用連續的物理內存造成的碎片問題。

物理內存管理

假設一個可用的物理內存有4GB的空間。按照一個頁框的大小4KB劃分,4GB的空間就是4GB/4KB = 1048576個頁框。有這么多的物理頁,操作系統肯定是要對它們進行管理的,OS要知道哪些頁被使用,哪些頁在空閑。

Linux內核里面使用了struct page結構描述系統中的每個物理頁,為了節省內存(這個結構體本身也是在內核,也是在內存里面的)這里面使用了大量的聯合體union

/* include/linux/mm_types.h */
struct page
{/* 原?標志,有些情況下會異步更新 */unsigned long flags;union{struct{/* 換出?列表,例如由zone->lru_lock保護的active_list */struct list_head lru;/* 如果最低為為0,則指向inode* address_space,或為NULL* 如果?映射為匿名內存,最低為置位* ?且該指針指向anon_vma對象*/struct address_space *mapping;/* 在映射內的偏移量 */pgoff_t index;/** 由映射私有,不透明數據* 如果設置了PagePrivate,通常?于buffer_heads* 如果設置了PageSwapCache,則?于swp_entry_t* 如果設置了PG_buddy,則?于表?伙伴系統中的階*/unsigned long private;};struct{ /* slab, slob and slub */union{struct list_head slab_list; /* uses lru */struct{ /* Partial pages */struct page *next;
#ifdef CONFIG_64BITint pages;    /* Nr of pages left */int pobjects; /* Approximate count */
#elseshort int pages;short int pobjects;
#endif};};struct kmem_cache *slab_cache; /* not slob *//* Double-word boundary */void *freelist; /* first free object */union{void *s_mem;            /* slab: first object */unsigned long counters; /* SLUB */struct{                        /* SLUB */unsigned inuse : 16; /* ?于SLUB分配器:對象的數? */unsigned objects : 15;unsigned frozen : 1;};};};...};union{/* 內存管理?系統中映射的?表項計數,?于表??是否已經映射,還?于限制逆向映射搜索*/atomic_t _mapcount;unsigned int page_type;unsigned int active; /* SLAB */int units;           /* SLOB */};...
#if defined(WANT_PAGE_VIRTUAL)/* 內核虛擬地址(如果沒有映射則為NULL,即?端內存) */void *virtual;
#endif /* WANT_PAGE_VIRTUAL */...
}

重要參數

  1. flags:用來存放頁的狀態。包括是不是臟的,是不是被鎖定在內存中等等。flags的每一位單獨表示一種狀態,所以他至少可以同時表示出32種不同的狀態。這些標志定義在<linux/page-flags.h>中。其中?些比特位非常重要,如PG_locked?于指定頁是否鎖定,PG_uptodate?于表示頁的數據已經從塊設備讀取并且沒有出現錯誤。
  2. _mapcount:表示在頁表中有多少項指向該頁,也就是這一頁被引?了多少次。當計數值變為-1時,就說明當前內核并沒有引?這一頁,于是在新的分配中就可以使用它。
  3. virtual:是頁的虛擬地址。通常情況下,它就是頁在虛擬內存中的地址。有些內存(即所謂的?端內存)并不永久地映射到內核地址空間上。在這種情況下,這個域的值為NULL,需要的時候,必須動態地映射這些頁。

要注意的是struct page與物理頁相關,?并非與虛擬頁相關。?系統中的每個物理頁都要分配?個這樣的結構體,讓我們來算算對所有這些頁都這么做,到底要消耗掉多少內存。

struct page占40個字節的內存吧,假定系統的物理頁為 4KB ??,系統有 4GB 物理內存。那么系統中共有頁面 1048576 個(1兆個),所以描述這么多??的page結構體消耗的內存只不過40MB ,相對系統 4GB 內存而言,僅是很小的?部分罷了。因此,要管理系統中這么多物理??,這個代價并不算太大。

要知道的是,頁的大小對于內存利?和系統開銷來說非常重要,頁太大,頁必然會剩余較?不能利?的空間(頁內碎片)。頁太小,雖然可以減小頁內碎片的大小,但是頁太多,會使得頁表太長而占?內存,同時系統頻繁地進行頁轉化,加重系統開銷。因此,頁的大小應該適中,通常為 512B -8KBwindows系統的頁框大小為4KB

頁表

頁表中的每一個表項都指向一個物理頁的起始地址。在32位機器上要將4GB的空間全部用頁表指向的話需要4GB/4KB = 1048576個表項。

頁表中的物理地址,和真實的物理頁之間是隨機映射關系,哪里可用就指向哪里。**最終使用的物理內存是離散的,但是虛擬地址是連續的。**處理器在訪問數據、獲取指令都是用的虛擬地址,最終都能通過頁表找到物理地址。

我們可以算一下,在32位機器上,地址長度為4字節,頁表占用的空間為1048576 * 4 = 4MB,映射頁表本身就要消耗4MB / 4KB = 1024個物理頁。每次新起一個進程,都要將這占用了1024個物理頁的頁表加載映射到內存,但是根據局部性原理,一個進程在一段時間內只需要訪問某幾個頁就可以正常運行了。所以根本沒有必要將所有的物理頁都常駐內存。

解決這一問題的方法就是對頁表再次分頁,即多級頁表。

我們將單一的一個頁表再次拆分為1024個更小的映射表。1024(每個表中的表項數)* 1024(表的個數)仍然可以覆蓋4GB的物理內存空間。

image-20250415173654528

這里的每一個頁表就是真正的頁表了,一共有1024個頁表,一個頁表占用4KB,一共也就是4MB。雖然和之前沒區別,但是一個應用程序不可能把這些頁表全部用完的,這樣設計方便用多少,開多少。例如:一個程序的代碼段、數據段、棧、一共只需要10MB的空間,那么使用3個頁表就夠用了。(一個頁表能覆蓋4MB的物理空間)

頁目錄結構

那么每一個頁框都有一個頁表項來指向,這1024個頁表也需要被管理起來。管理頁表的表稱為頁目錄表,形成了二級頁表:

image-20250415174355634

所有頁表的物理地址都被頁目錄表指向

頁目錄的物理地址被CR3寄存器指向,此寄存器保存了當前正在執行任務的頁目錄地址。

所以在程序被加載的時候,不僅要為程序的內容分配物理內存,還需要為保存程序物理地址和虛擬地址映射的頁目錄和頁表分配物理內存。

兩級頁表的地址轉換

下面以一個邏輯地址為例。將邏輯地址0000000000,0000000001,111111111111轉化為物理地址的過程:

  1. 在32為處理器中,采用4KB頁大小,則虛擬地址中的低12為為頁偏移,剩下的高20位給頁表分成兩級,每個級占10位。
  2. CR3寄存器讀取頁目錄起始地址,再根據一級頁號查頁目錄表,找到下一級頁表在物理內存中存放位置。
  3. 根據二級頁號查表,找到最終想要訪問的內存塊號。
  4. 結合頁內偏移量得到物理地址。

image-20250415175946257

  • 一個物理頁的地址,一定是對齊4KB的(最后的12位全為0),所以其實只需要記錄物理頁地址的高20位即可。
  • 以上就是MMU的工作流程,Memory Manage Unit是一種硬件電路,其速度很快,主要工作是做內存管理,地址轉換只是其工作之一。

那么這個工作流就沒有缺點了嗎?當然是有的!MMU進行兩次查詢確定物理地址,在確認了權限問題后,將這個物理地址發到總線,內存收到后開始讀取數據并返回。當頁表變為N級時需要經過N次檢索+1次讀寫,查詢效率就會變低。

那么有沒有提升效率的方法呢?有的,那就是添加一層中間層也就是TLB江湖人稱“快表”,其實也就是緩存。當 CPUMMU 傳新虛擬地址之后, MMU 先去問 TLB 那邊有沒有,如果有就直接拿到物理地址發到總線給內存,?活。但 TLB 容量比較小,難免發生 Cache Miss ,這時候 MMU 還有保底的?武器頁表,在頁表中找到之后 MMU 除了把地址發到總線傳給內存,還把這條映射關系給到TLB,讓它記錄?下刷新緩存。

image-20250415180831926

缺頁異常

設想,CPUMMU 的虛擬地址,在 TLB 和頁表都沒有找到對應的物理頁,該怎么辦呢?其實這就是缺頁異常 Page Fault ,它是?個由硬件中斷觸發的可以由軟件邏輯糾正的錯誤。假如目標內存頁在物理內存中沒有對應的物理頁或者存在但?對應權限,CPU 就?法獲取數據,這種情況下CPU就會報告?個缺頁錯誤。由于 CPU 沒有數據就無法進行計算,CPU罷工了用戶進程也就出現了缺頁中斷,進程會從用戶態切換到內核態,并將缺頁中斷交給內核的 Page Fault Handler 處理。

image-20250415181131773

缺頁中斷會交給PageFaultHandler處理,其根據缺頁中斷的不同類型進行不同的處理:

  • Hard Page Fault 也被稱為 Major Page Fault ,翻譯為硬缺頁錯誤/主要缺頁錯誤,這時物理內存中沒有對應的物理頁,需要CPU打開磁盤設備讀取到物理內存中,再讓MMU建立虛擬地址和物理地址的映射。
  • Soft Page Fault 也被稱為 Minor Page Fault ,翻譯為軟缺頁錯誤/次要缺頁錯誤,這時物理內存中是存在對應物理頁的,只不過可能是其他進程調入的,發出缺頁異常的進程不知道?已,此時MMU只需要建立映射即可,無需從磁盤讀取寫入內存,?般出現在多進程共享內存區域。
  • Invalid Page Fault 翻譯為無效缺頁錯誤,比如進程訪問的內存地址越界訪問,?比如對空指針解引用內核就會報 segment fault 錯誤中斷進程直接掛掉。

Linux進程VS線程

線程優點

  • 創建?個新線程的代價要比創建?個新進程小得多
  • 與進程之間的切換相比,線程之間的切換需要操作系統做的?作要少很多
  • 最主要的區別是線程的切換虛擬內存空間依然是相同的,但是進程切換是不同的。這兩種上下文切換的處理都是通過操作系統內核來完成的。內核的這種切換過程伴隨的最顯著的性能損耗是將寄存器中的內容切換出。
  • 另外?個隱藏的損耗是上下文的切換會擾亂處理器的緩存機制。簡單的說,?旦去切換上下文,處理器中所有已經緩存的內存地址?瞬間都作廢了。還有?個顯著的區別是當你改變虛擬內存空間的時候,處理的頁表緩沖 TLB (快表)會被全部刷新,這將導致內存的訪問在?段時間內相當的低效。但是在線程的切換中,不會出現這個問題,當然還有硬件cache
  • 線程占?的資源要比進程少很多
  • 能充分利用多處理器的可并行數量
  • 在等待慢速I/O操作結束的同時,程序可執?其他的計算任務
  • 計算密集型應用,為了能在多處理器系統上運行,將計算分解到多個線程中實現
  • I/O密集型應?,為了提高性能,將I/O操作重疊。線程可以同時等待不同的I/O操作。

線程缺點

  • 性能損失

?個很少被外部事件阻塞的計算密集型線程往往?法與其它線程共享同?個處理器。如果計算密集型線程的數量比可用的處理器多,那么可能會有較大的性能損失,這里的性能損失指的是增加了額外的同步和調度開銷,而可?的資源不變。

  • 健壯性降低

編寫多線程需要更全?更深入的考慮,在?個多線程程序?,因時間分配上的細微偏差或者因共享了不該共享的變量?造成不良影響的可能性是很大的,換句話說線程之間是缺乏保護的。

  • 缺乏訪問控制

進程是訪問控制的基本粒度,在?個線程中調?某些OS函數會對整個進程造成影響。

  • 編程難度提高

編寫與調試?個多線程程序比單線程程序困難得多

線程異常

  • 單個線程如果出現除零,野指針問題導致線程崩潰,進程也會隨著崩潰
  • 線程是進程的執?分?,線程出異常,就類似進程出異常,進而觸發信號機制,終止進程,進程終止,該進程內的所有線程也就隨即退出

用途

  • 合理的使用多線程,能提高CPU密集型程序的執行效率
  • 合理的使用多線程,能提高IO密集型程序的用戶體驗(如生活中我們?邊寫代碼?邊下載開發?具,就是多線程運行的?種表現)

進程和線程

  • 進程是資源分配的基本單位
  • 線程是調度的基本單位
  • 線程共享進程數據,但也擁有??的?部分數據: 1、線程ID 2、?組寄存器 3、棧 4、errno 5、信號屏蔽字 6、調度優先級

進程的多個線程共享

同?地址空間,因此Text SegmentData Segment都是共享的,如果定義?個函數,在各線程中都可以調?,如果定義?個全局變量,在各線程中都可以訪問到,除此之外,各線程還共享以下進程資源和環境:

  • 文件描述符表
  • 每種信號的處理?式(SIG_IGNSIG_DFL或者?定義的信號處理函數)
  • 當前工作目錄
  • 用戶id和組id

進程和線程的關系如下圖:

image-20250415230714478

對于之前學習的進程和這個線程是相悖的嗎?并不是,進程就可以看作一個有一個線程執行流的進程。

Linux線程控制

POSIX線程庫

  • 與線程有關的函數構成了?個完整的系列,絕大多數函數的名字都是以“pthread_”打頭的
  • 要使用這些函數庫,要通過引入頭文件<pthread.h>
  • 鏈接這些線程函數庫時要使用編譯器命令的-lpthread選項

創建線程

功能:創建?個新的線程原型:int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);
參數:thread:返回線程IDattr:設置線程的屬性,attr為NULL表示使用默認屬性start_routine:是個函數地址,線程啟動后要執行的函數arg:傳給線程啟動函數的參數返回值:成功返回0;失敗返回錯誤碼

錯誤檢查:

  • 傳統的?些函數是,成功返回0,失敗返回-1,并且對全局變量errno賦值以指示錯誤
  • pthreads函數出錯時不會設置全局變量errno(??部分其他POSIX函數會這樣做)。?是將錯誤代碼通過返回值返回
  • pthreads同樣也提供了線程內的errno變量,以支持其它使用errno的代碼。對于pthreads函數的錯誤,建議通過返回值來判定,因為讀取返回值要比讀取線程內的errno變量的開銷更小
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>
void *rout(void *arg) {int i;for( ; ; ) {printf("I'am thread 1\n");sleep(1);}
}
int main( void )
{pthread_t tid;int ret;if ( (ret=pthread_create(&tid, NULL, rout, NULL)) != 0 ) {fprintf(stderr, "pthread_create : %s\n", strerror(ret));exit(EXIT_FAILURE);}int i;for(; ; ) {printf("I'am main thread\n");sleep(1);}
}
#include <pthread.h>
// 獲取線程ID
pthread_t pthread_self(void);

這個函數返回pthread_t類型的值,指代的是調用pthread_self函數的線程id。這個idpthread庫給每個線程定義的進程內唯一標識,是pthread庫維持的。沒錯,在內核層面根本沒有“線程”這個概念,有的只是進程,只是PCB其它的東西都是庫給我們封裝的。Linux下是根據進程PCB重復利用,搞成了線程,windows下則是另起爐灶,搞出來了一套新的東西。

由于每個進程都有自己獨立的內存空間,故這個id的作用域是進程級的而不是系統級(內核不認識)

其實pthread庫中也是通過內核提供的系統調用clone...來創建線程的,而內核會為每個線程創建系統全局唯一的id來唯一標識這個線程。

我們可以使用PS命令查看線程信息:

$ ps -aL | head -1 && ps -aL | grep mythreadPID     LWP     TTY     TIME      CMD
2711838 2711838 pts/235 00:00:00 mythread
2711838 2711839 pts/235 00:00:00 mythread-L 選項:打印線程信息

其中LWP就是真正的線程id可以使用gettid()調用,之前使用 pthread_self 得到的這個數實際上是一個地址,在虛擬地址空間上的一個地址,通過這個地址,可以找到關于這個線程的基本信息,包括線程ID,線程棧,寄存器等屬性。

ps -aL 得到的線程ID,有?個線程ID和進程ID相同,這個線程就是主線程,主線程的棧在虛擬地址空間的棧上,而其他線程的棧在是在共享區(堆棧之間),因為pthread系列函數都是pthread庫提供給我們的。而pthread庫是在共享區的。所以除了主線程之外的其他線程的棧都在共享區。

線程終止

如果需要止終止某個線程而不終止整個進程,可以有三種方法:

  1. 從線程函數return。這種方法對主線程不適?,從main函數return相當于調用exit
  2. 線程可以調用pthread_exit終止自己。
  3. ?個線程可以調用pthread_cancel終止同一進程中的另?個線程

pthread_exit函數

功能:線程終?
原型:void pthread_exit(void *value_ptr);
參數:value_ptr:value_ptr不要指向一個局部變量。
返回值:無返回值,跟進程一樣,線程結束的時候無法返回到它的調用者(自身)

注意:pthread_exit函數中的輸出型參數指向的內存必須是全局的,或者用malloc在堆上開辟的空間,不能指向在線程函數的棧上分配的空間,因為當其他線程得到這個返回值時,這個線程已經退出,會導致非法訪問等內存問題。

pehread_cancel函數

功能:取消?個執行中的線程
原型:int pthread_cancel(pthread_t thread);
參數:thread:線程ID
返回值:成功返回0; 失敗返回錯誤碼

線程等待

已經退出的線程,其地址空間沒有被釋放,仍然在進程地址空間內占據位置,新起來的線程也需要空間,所以需要回收退出線程的空間。

功能:等待線程結束
原型:int pthread_join(pthread_t thread, void **value_ptr);
參數:thread:線程IDvalue_ptr:它指向?個指針,后者指向線程的返回值
返回值:成功返回0; 失敗返回錯誤碼

調用該函數的線程將掛起等待,直到要join的線程終止。被join的線程以不同的方法終止,通過pthread_join得到的終止狀態時不同的,總結如下:

  1. 如果thread線程通過return返回,value_ptr所指向的單元?存放的是thread線程函數的返回值。
  2. 如果thread線程被別的線程調?pthread_cancel異常終掉,value_ptr所指向的單元?存放的是常數PTHREAD_CANCELED
  3. 如果thread線程是自己調用pthread_exit終?的,value_ptr所指向的單元存放的是傳給pthread_exit的參數。
  4. 如果對thread線程的終止狀態不感興趣,可以傳NULLvalue_ptr參數。

分離線程

默認情況下,新創建的線程是joinable的,線程退出后,需要對其進行pthread_join操作,否則無法釋放資源,從而造成系統泄漏。

如果不關心線程的返回值,join是?種負擔,這個時候,我們可以告訴系統,當線程退出時,自動釋放線程資源。

int pthread_detach(pthread_t thread);

可以是線程組內對其它線程進行分離,亦可以是線程自己分離

pthread_detach(pthread_self());

線程被分離了就不是joinable的了

線程ID及進程地址空間布局

  • pthread_create函數會產??個線程ID,存放在第?個參數指向的地址中。該線程ID和前?說的線程ID不是?回事。前面講的線程ID(LWP)屬于進程調度的范疇。因為線程是輕量級進程,是操作系統調度器的最小單位,所以需要?個數值來唯?表示該線程。
  • pthread_create函數第?個參數指向?個虛擬內存單元,該內存單元的地址即為新創建線程的線程ID,屬于NPTL線程庫的范疇。
  • 線程庫的后續操作,就是根據該線程ID來操作線程的。線程庫NPTL提供了pthread_self函數,可以獲得線程??的ID:
pthread_t pthread_self(void);
特征LWPpthread_self()返回值
作用范圍內核空間(系統級調度)用戶空間(線程庫級操作)
唯一性全局唯一(整個系統)進程內唯一
數據類型pid_t(通常為整數)pthread_t(具體實現依賴系統)
獲取方式syscall(SYS_gettid)gettid()pthread_self()
用途內核調度、系統調用、信號處理線程庫函數參數(如pthread_equal

關于pthread_t的類型,取決于具體實現,對于Linux目前實現的NPTL而言,pthread_t類型的線程ID,本質就是一個進程地址空間上的一個地址。

image-20250418225946403

image-20250418230053962

線程封裝

最新線程封裝具體參考:

25/Udp/chat/thread.hpp · 欽某/Code - 碼云 - 開源中國 (gitee.com)

Code/25/Udp/chat/thread.hpp at master · QinMou000/Code (github.com)

沒什么好說的,注意detach的順序就行,造一次輪子就行了。
_t`類型的線程ID,本質就是一個進程地址空間上的一個地址。**

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/76232.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/76232.shtml
英文地址,請注明出處:http://en.pswp.cn/web/76232.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

人臉識別聯合行為檢測的辦公管理新模式

基于人臉識別與行為檢測的辦公智能化解決方案 一、背景 在傳統辦公場景中&#xff0c;員工考勤管理、工位使用情況統計、安全監控等環節存在諸多痛點。例如&#xff0c;傳統考勤方式如指紋打卡、刷卡等存在代打卡現象&#xff0c;考勤數據不準確&#xff1b;對于員工是否在工…

ceph weight 和 reweight 的區別

ceph osd df ID CLASS WEIGHT REWEIGHT SIZE RAW USE DATA OMAP META AVAIL %USE VAR PGS STATUS0 nvme 6.98630 0.95508 7.0 TiB 5.0 TiB 4.9 TiB 13 GiB 33 GiB 2.0 TiB 71.10 0.96 83 up1 nvme 6.98630

WInform當今技術特性分析

Windows Forms (WinForms) 技術特性分析 引言 Windows Forms (WinForms) 作為微軟最早推出的基于.NET的圖形用戶界面開發框架&#xff0c;已經存在了20多年。在如今充滿了各種現代UI框架的軟件開發生態系統中&#xff0c;WinForms仍然保持著其獨特的地位。本文將深入分析WinF…

Spark rdd算子解析與實踐

一、RDD基礎回顧 RDD&#xff08;Resilient Distributed Dataset&#xff09; 是Spark的核心抽象&#xff0c;代表一個不可變、分區的分布式數據集合。其核心特性包括&#xff1a; 容錯性&#xff1a;通過血緣&#xff08;Lineage&#xff09;記錄數據生成過程&#xff0c;支…

sqlite3的API以及命令行

sqlite是目前最流行的嵌入式數據庫。 所謂嵌入式&#xff0c;就是足夠簡單&#xff0c;可以嵌入到我們自己開發的應用程序之中。 在Linux系統中&#xff0c;sqlite的使用只需要使用它的API&#xff0c;連接它的動態連接庫&#xff0c;甚至都不用連接&#xff0c;sqlite的實現…

Allure測試報告按測試終端和測試類型智能分類查看

以下是實現Allure測試報告按測試終端和測試類型智能分類的完整方案: 一、測試框架分層設計 # 項目結構 project/ ├── api_tests/ # API測試 │ └── test_order.py ├── app_tests/ # 移動端測試 │ ├── android/ │ └── ios/ ├── pc_te…

Spine-Leaf 與 傳統三層架構:全面對比與解析

本文將詳細介紹Spine-Leaf架構&#xff0c;深入對比傳統三層架構&#xff08;Core、Aggre、Access&#xff09;&#xff0c;并探討其與Full-mesh網絡和軟件定義網絡&#xff08;SDN&#xff09;的關聯。通過通俗易懂的示例和數據中心網絡分析&#xff0c;我將幫助您理解Spine-L…

圖像預處理-圖像噪點消除

一.基本介紹 噪聲&#xff1a;指圖像中的一些干擾因素&#xff0c;也可以理解為有那么一些點的像素值與周圍的像素值格格不入。常見的噪聲類型包括高斯噪聲和椒鹽噪聲。 濾波器&#xff1a;也可以叫做卷積核 - 低通濾波器是模糊&#xff0c;高通濾波器是銳化 - 低通濾波器就…

安卓手機如何改ip地址教程

對于安卓手機用戶而言&#xff0c;ip修改用在電商、跨境電商、游戲搬磚、社交軟件這些需要開多個賬號的項目。因為多個設備或賬號又不能在同一ip網絡下&#xff0c;所以修改手機的IP地址防檢測成為一個必要的操作。以下是在安卓手機上更改IP地址的多種方法及詳細步驟&#xff0…

對象池模式在uniapp鴻蒙APP中的深度應用

文章目錄 對象池模式在uniapp鴻蒙APP中的深度應用指南一、對象池模式核心概念1.1 什么是對象池模式&#xff1f;1.2 為什么在鴻蒙APP中需要對象池&#xff1f;1.3 性能對比數據 二、uniapp中的對象池完整實現2.1 基礎對象池實現2.1.1 核心代碼結構2.1.2 在Vue組件中的應用 2.2 …

本地部署大模型實現掃描版PDF文件OCR識別!

在使用大模型處理書籍 PDF 時&#xff0c;有時你會遇到掃描版 PDF&#xff0c;也就是說每一頁其實是圖像形式。這時&#xff0c;大模型需要先從圖片中提取文本&#xff0c;而這就需要借助 OCR&#xff08;光學字符識別&#xff09;技術。 像 Gemini 2.5 這樣的強大模型&#x…

《Operating System Concepts》閱讀筆記:p700-p732

《Operating System Concepts》學習第 60 天&#xff0c;p700-p732 總結&#xff0c;總計 33 頁。 一、技術總結 1.Virtual machine manager (VMM) The computer function that manages the virtual machine; also called a hypervisor. VMM 也稱為 hypervisor。 2.types …

軟件項目驗收報告模板

軟件項目驗收報告 一、項目基本信息 項目名稱XX智能倉儲管理系統開發單位XX科技有限公司驗收單位XX物流集團合同簽訂日期2023年3月15日項目啟動日期2023年4月1日驗收日期2024年1月20日 二、驗收范圍 入庫管理模塊&#xff08;包含RFID識別、庫存預警&#xff09;出庫調度模…

深度學習筆記39_Pytorch文本分類入門

&#x1f368; 本文為&#x1f517;365天深度學習訓練營 中的學習記錄博客&#x1f356; 原作者&#xff1a;K同學啊 | 接輔導、項目定制 一、我的環境 1.語言環境&#xff1a;Python 3.8 2.編譯器&#xff1a;Pycharm 3.深度學習環境&#xff1a; torch1.12.1cu113torchvision…

二分查找-LeetCode

題目 給定一個 n 個元素有序的&#xff08;升序&#xff09;整型數組 nums 和一個目標值 target&#xff0c;寫一個函數搜索 nums 中的 target&#xff0c;如果目標值存在返回下標&#xff0c;否則返回 -1。 示例 1: 輸入: nums [-1,0,3,5,9,12], target 9 輸出: 4 解釋: …

從 Ext 到 F2FS,Linux 文件系統與存儲技術全面解析

與 Windows 和 macOS 操作系統不同&#xff0c;Linux 是由愛好者社區開發的大型開源項目。它的代碼始終可供那些想要做出貢獻的人使用&#xff0c;任何人都可以根據個人需求自由調整它&#xff0c;或在其基礎上創建自己的發行版本。這就是為什么 Linux 存在如此多的變體&#x…

leetcode:3210. 找出加密后的字符串(python3解法)

難度&#xff1a;簡單 給你一個字符串 s 和一個整數 k。請你使用以下算法加密字符串&#xff1a; 對于字符串 s 中的每個字符 c&#xff0c;用字符串中 c 后面的第 k 個字符替換 c&#xff08;以循環方式&#xff09;。 返回加密后的字符串。 示例 1&#xff1a; 輸入&#xff…

JVM詳解(曼波腦圖版)

(?ω?)&#xff89; 好噠&#xff01;曼波會用最可愛的比喻給小白同學講解JVM&#xff0c;準備好開啟奇妙旅程了嗎&#xff1f;(??????)? &#x1f4cc; 思維導圖 ━━━━━━━━━━━━━━━━━━━ &#x1f34e; JVM是什么&#xff1f;&#xff08;蘋果式比…

ZStack文檔DevOps平臺建設實踐

&#xff08;一&#xff09;前言 對于軟件產品而言&#xff0c;文檔是不可或缺的一環。文檔能幫助用戶快速了解并使用軟件&#xff0c;包括不限于特性概覽、用戶手冊、API手冊、安裝部署以及場景實踐教程等。由于軟件與文檔緊密耦合&#xff0c;面對業務的瞬息萬變以及軟件的飛…

Git創建分支操作指南

1. 創建新分支但不切換&#xff08;僅創建&#xff09; git branch <分支名>示例&#xff1a;創建一個名為 new-feature 的分支git branch new-feature2. 創建分支并立即切換到該分支 git checkout -b <分支名> # 傳統方式 # 或 git switch -c <分支名&g…