線程概念與控制

目錄

Linux線程概念

什么是線程

分頁式存儲管理

虛擬地址和頁表的由來

物理內存管理

頁表

缺頁異常

線程的優點

線程的缺點

線程異常

Linux進程VS線程

進程與線程

進程的多個線程共享

進程與線程關系如圖

Linux線程控制

POSIX線程庫

創建線程

測試

獲取線程ID

線程終止

線程等待

測試

分離線程

測試

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

線程封裝

測試


Linux線程概念
?

什么是線程
?

1.在?個程序?的?個執行路線就叫做線程(thread)。更準確的定義是:線程是“?個進程內部
的控制序列”
2.?切進程至少都有?個執行線程

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

4.在Linux系統中,在CPU眼中,看到的PCB都要比傳統的進程更加輕量化

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

分頁式存儲管理

虛擬地址和頁表的由來


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

但是不同的程序他的代碼與數據長度都是不一樣的,并且一直會有進程退出,如果采用連續內存的方式就會導致存在很多的內存碎片。

因此,我們希望操作系統提供給用戶的空間必須是連續的,但是物理內存最好不要連續。

由此,虛擬地址與頁表就誕生了

把物理內存按照?個固定的長度的頁框進行分割,有時叫做物理頁。每個頁框包含?個物理頁

?個頁的大小等于頁框的大小。大多數 32位 體系結構支持4KB的頁,而64位體系結
構?般會支持?8KB 的頁。

頁:一個數據塊,存放于頁框或磁盤上

頁框:一個存儲區域

有了這種機制,CPU便并非是直接訪問物理內存地址,而是通過虛擬地址空間來間接的訪問物理內存地址。所謂的虛擬地址空間,是操作系統為每?個正在執行的進程分配的?個邏輯地址。
?

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

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

物理內存管理
?

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

內核用?struct page 結構表示系統中的每個物理頁,出于節省內存的考慮, struct page 中使
用了大量的聯合體union

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

我們算一個struct page 40個字節,一個頁4kb,那么一個struct page的總數大概為頁總數的1/100。

系統4GB的空間,struct page大概占40M,相對于系統的4GB內存來說并不算多

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

頁表
?

頁表中的每?個表項,指向?個物理頁的開始地址。在 32 位系統中,虛擬內存的最大空間是 4GB ,這是每?個用戶程序都擁有的虛擬內存空間。既然需要讓 4GB 的虛擬內存全部可用,那么頁表中就需要能夠表示這所有的 4GB 空間,那么就一共需要 4GB/4KB = 1048576 個表項。

虛擬內存仍然是連續的,圖中的虛線只是用來表示虛擬內存單元與頁表每一個表項的映射關系

最終實現,虛擬地址上連續,物理地址上分散,并且解決了內存碎片化的問題

提問

在 32 位系統中,地址的長度是 4 個字節,那么頁表中的每?個表項就是占用?4 個字節。所以頁表占據的總空間大小就是: 1048576*4 = 4MB的大小。也就是說映射表自己本身,就要占用4MB /4KB = 1024 個物理頁。這會存在哪些問題呢?

1.我們創建頁表的目的就是為了將進程劃分為可以一個個頁,可以不用連續的存放在物理地址。

但是我們頁表自己就需要1024個連續物理頁,與我們一開始的想法沖突

2.很多時候進程都是需要訪問部分物理頁,沒有必要讓所有物理頁都一直占據內存空間

解答

解決大容量頁表的方法就是將頁表也看作文件,對頁表也進行分頁,因此,多級頁表的思想就產生了。

將一個大頁表拆成1024個小頁表(每個表1024個表項),這樣,1024(表的個數)*1024(每個表中表項)個4k的小頁表同樣可以占據4GB的物理內存空間。

從總數上看,整張大頁表仍然需要4M空間,似乎和之前沒區別,但實際上一個應用程序不可能占據4GB的所有內存空間,或許幾十個小頁表就夠了,一個程序的代碼段,數據段,棧段一共需要10M,也就是3張小頁表就夠了

缺頁異常
?

CPU給MMU的虛擬地址,在 TLB 和頁表都沒有找到對應的物理頁,該怎么辦呢?其實這就是缺頁異常 Page Fault ,它是?個由硬件中斷觸發的可以由軟件邏輯糾正的錯誤。

由于CPU沒有數據就無法進行計算,CPU罷工了,用戶進程也就出現了缺頁中斷,進程會從用戶態切換到內核態,并將缺頁中斷交給內核的Page Fault Handler處理。

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

1.Hard Page Fault 也被稱為 Major Page Fault ,翻譯為硬缺頁錯誤/主要缺頁錯誤,這
時物理內存中沒有對應的物理頁,需要CPU打開磁盤設備讀取到物理內存中,再讓MMU建立虛擬
地址和物理地址的映射。

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

線程的優點
?

1.創建一個新線程代價比創建一個新進程代價小得多

2.與進程切換相比,線程切換操作系統要做的工作也小很多

例如:1.由于線程屬于同一個進程,擁有相同的虛擬地址空間。2.進程上下文的切換會擾亂處理器的緩存機制,這將導致內存的訪問在一段時間內效率很低,在線程的切換中就沒有這個問題。

3.線程占用的資源要比進程小很多

4.能充分利用多處理器的可并行數量

5.在等待慢速IO工作完成的同時也可以執行計算任務

6.計算密集型應用為了在多處理器系統上運行,將計算分解到多線程中運行

7.IO密集型應用,為了提高性能將I/O操作重疊。線程可同時等待不同的I/O操作

線程的缺點
?

性能損失
?個很少被外部事件阻塞的計算密集型線程往往無法與其它線程共享同?個處理器。如果計
算密集型線程的數量?可?的處理器多,那么可能會有較大的性能損失,這?的性能損失指
的是增加了額外的同步和調度開銷,而可用的資源不變。
健壯性降低
編寫多線程需要更全?更深?的考慮,在?個多線程程序里,因時間分配上的細微偏差或者
因共享了不該共享的變量?造成不良影響的可能性是很大的,換句話說線程之間是缺乏保護
的。
缺乏訪問控制
進程是訪問控制的基本粒度,在?個線程中調用某些OS函數會對整個進程造成影響。
編程難度提高

線程異常
?

一個進程中的多個線程同屬于該進程,那就意味著一旦某個進程出現異常例如野指針導致的不僅僅是那個線程退出,還是整個進程的退出。因為線程出了異常就是進程出異常。

Linux進程VS線程
?

進程與線程

1.進程是資源分配的基本單位(所有線程共享進程資源)

2.線程是調度的基本單位(這意味著每個線程分配到的時間片和進程本身無關)

3.線程共享一部分數據,同時還擁有自己的一部分數據:

線程ID、一組寄存器、棧、errno信號屏蔽字、調度優先級

進程的多個線程共享
?

同?地址空間,因此Text Segment、Data Segment都是共享的,如果定義?個函數,在各線程中都可以調用,如果定義?個全局變量,在各線程中都可以訪問到,除此之外,各線程還共享以下進程資源和環境:
文件描述符表、每種信號的處理方式、當前工作目錄、用戶ID與組ID

進程與線程關系如圖

Linux線程控制
?

POSIX線程庫

1.與線程有關的函數構成了?個完整的系列,絕大多數函數的名字都是以“pthread_”打頭的

2.要使用這些函數庫,要引入頭文件<pthread.h>

3.連接這些函數庫需要使用編譯器命令的 -lpthread選項,也就是使用系統路徑下的libpthread.so 動態庫,頭文件與庫文件都在系統默認路徑下

創建線程

#include<pthread.h>int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *
(*start_routine)(void*), void *arg);thread:返回線程ID
attr:設置線程屬性,設置為nullptr則采用默認配置
start_routine:是個函數地址,線程啟動后要執?的函數
arg:傳給線程啟動函數的參數返回值:成功返回0
錯誤返回錯誤碼

測試

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<string.h>
void* run(void* arg)
{int i=0;while(true){i++;printf("我是線程1:%d\n",i);sleep(1);}
}int main()
{pthread_t tid;int ret;if(ret=pthread_create(&tid,nullptr,run,nullptr)!=0){fprintf(stderr,"pthread create:%s",strerror(ret));exit(EXIT_FAILURE);}while(true){printf("我是主線程\n");sleep(1);}
}

獲取線程ID

#include<pthread.h>pthread_t pthread_self(void);

這個函數返回“進程級線程ID

這個“ID”是pthread庫給每個線程定義的進程內唯?標識,是pthread庫維持的

當然了,這個ID是進程級的,操作系統并不認識

其實pthread庫也是通過內核提供的系統調?(例如clone)來創建線程的,而內核會為每個線程創建系統全局唯?的“ID”來唯?標識這個線程。

-L 選項:打印線程信息
?

我們可以看到,兩個線程擁有相同的進程ID,但是操作系統為他們分配了不同的“LWP(即系統級線程ID)

LWP得到的是真正的線程ID。之前使?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不要指向?個局部變量,因為這個返回值是要交給其他線程查看的,
如果指向局部變量那么線程結束時,局部變量也會被一起銷毀
因此,value_ptr最好是全局變量的地址或者是堆上開辟的空間?返回值,跟進程?樣,線程結束的時候?法返回到它的調?者(??)

如果采用return返回,那么要求也和pthread_exit相同,不要指向一個局部變量
?

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

線程等待
?

為什么需要線程等待

已經退出的線程,其空間沒有被釋放,仍然在進程的地址空間內。
創建新的線程不會復用剛才退出線程的地址空間。
?

這里其實和進程等待差不多,如果不等待的話那么線程也會一直占有空間不釋放,相當于“僵尸線程

功能:等待線程結束int pthread_join(pthread_t thread, void **value_ptr);thread:指定線程IDvalue_ptr:因為線程的返回值是一個一級指針,
所以我們就需要使用二級指針才能拿到線程退出時返回的值返回值:
成功返回0
失敗返回錯誤碼

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

1.使用return 或pthread_exit函數退出,我們知道exit和return非常類似,所以使用這兩種方式退出時,value_ptr指針上的值就是return的返回值或時傳給pthread_exit函數的參數。

2.如果線程是被別的線程使用pthread_cancel終止掉的,value_ptr所指向的單元里存放的是
就為常數PTHREAD_CANCELED

3.如果對thread線程的終止狀態不感興趣,可以傳NULL給value_ptr參數

測試

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
void *thread1(void *arg)//1號線程return返回,在堆上開辟空間帶出返回值
{printf("thread 1 returning ... \n");int *p = (int *)malloc(sizeof(int));*p = 1;return (void *)p;
}
void *thread2(void *arg)//2號線程pthread_exit返回,一樣在堆上開辟空間
{printf("thread 2 exiting ...\n");int *p = (int *)malloc(sizeof(int));*p = 2;pthread_exit((void *)p);
}
void *thread3(void *arg)//3號線程被主線程pthread_cancel返回,返回值默認為常數PTHREAD_CANCELED
{while (1){ //printf("thread 3 is running ...\n");sleep(1);}return NULL;
}int  main(void)
{pthread_t tid;void *ret;// thread 1 returnpthread_create(&tid, NULL, thread1, NULL);pthread_join(tid, &ret);printf("thread return, thread id %lX, return code:%d\n", tid, *(int *)ret);free(ret);// thread 2 exitpthread_create(&tid, NULL, thread2, NULL);pthread_join(tid, &ret);printf("thread return, thread id %lX, return code:%d\n", tid, *(int *)ret);free(ret);// thread 3 cancel by otherpthread_create(&tid, NULL, thread3, NULL);sleep(3);pthread_cancel(tid);pthread_join(tid, &ret);if (ret == PTHREAD_CANCELED)printf("thread return, thread id %lX, return code:PTHREAD_CANCELED\n",tid);elseprintf("thread return, thread id %lX, return code:NULL\n", tid);
}

分離線程
?

默認情況下,新創建的線程是joinable的,線程退出后,需要對其進行pthread_join操作,否則
無法釋放資源,從而造成系統泄漏。
如果不關心線程的返回值,join是?種負擔,這個時候,我們可以告訴系統,當線程退出時,?
動釋放線程資源。

功能:分離線程,線程退出時自動釋放資源int pthread_detach(pthread_t thread);thread:指定線程ID返回值:
成功返回0
失敗返回錯誤碼

我們可以選擇讓線程自己分離自己,也可以選擇讓其他線程來進行分離

thread_detach(pthread_self());thread_datach(thread_id);


joinable和分離是沖突的,?個線程不能既是joinable又是分離的。


測試

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
void *thread_run(void *arg)
{pthread_detach(pthread_self());printf("%s\n", (char *)arg);return NULL;
}
int main(void)
{pthread_t tid;if (pthread_create(&tid, NULL, thread_run, (void*)"thread1 run...") != 0){printf("create thread error\n");return 1;}int ret = 0;sleep(1); // 很重要,要讓線程先分離,再等待if (pthread_join(tid, NULL) == 0){printf("pthread wait success\n");ret = 0;}else{printf("pthread wait failed\n");ret = 1;}return ret;
}

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

pthread_create函數會產生?個進程級線程ID,存放在第?個參數指向的地址中。
前面講的系統級線程ID屬于進程調度的范疇,因為線程是輕量級進程,是操作系統調度的最小單位,所以需要唯一的一個數值來表示該線程

pthread_create函數第?個參數指向?個虛擬內存單元,該內存單元的地址即為新創建線程的線
程ID,屬于NPTL線程庫的范疇。線程庫的后續操作,就是根據該線程ID來操作線程的。
?

線程庫NPTL提供了pthread_self函數,可以獲得線程自身的ID:
?

pthread_t pthread_self(void);

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

線程封裝


?

#pragma once
#include <iostream>
#include <pthread.h>
#include <functional>
#include <string>
#include <cstdint>namespace ThreadModule
{std::uint32_t cnt; // 原子計數器,形成線程名稱class Thread{public:using work_t = std::function<void()>;enum class TSTATUS{THREAD_NEW,THREAD_RUNNING,THREAD_STOP};Thread(work_t work) : _status(TSTATUS::THREAD_NEW),_joined(true),_func(work){SetName();}void EnableJoined(){if (_status == TSTATUS::THREAD_NEW)_joined = true;}void EnableDetach(){if (_status == TSTATUS::THREAD_NEW)_joined = false;}bool Start(){if (_status == TSTATUS::THREAD_RUNNING)return true;int n = pthread_create(&_id, nullptr, Run, (void *)this);if (n != 0)return false;return true;}bool Join(){if (_joined){int n = pthread_join(_id, nullptr);if (n != 0)return false;return true;}return _status==TSTATUS::THREAD_STOP;}~Thread() {}private:static void *Run(void *obj){Thread *self = static_cast<Thread *>(obj);self->_status = TSTATUS::THREAD_RUNNING;pthread_setname_np(self->_id, self->_name.c_str()); // 為當前線程取名字if (self->_joined == false)pthread_detach(pthread_self());self->_func();self->_status=TSTATUS::THREAD_STOP;return nullptr;}void SetName(){_name = "Thread-" + std::to_string(cnt++);}private:std::string _name;pthread_t _id;TSTATUS _status;bool _joined;work_t _func;};
}

測試

#include"thread.hpp"
#include<vector>
void test_printf(int num)
{std::cout<<num;
}
int main()
{std::vector<ThreadModule::Thread> vec;for(int i=0;i<10;++i){auto func=std::bind(test_printf,i);vec.emplace_back(func);}for(auto& e:vec)e.Start();for(auto& e:vec)e.Join();std::cout<<std::endl;return 0;
}

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

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

相關文章

Linux --進度條小程序更新

這里使用隨機數來模擬下載量&#xff0c;來實現一個下載進度更新的小程序 main.c 的代碼&#xff0c;其中downlod這個函數使用的是函數指針&#xff0c;如果有多個進度條函數可以傳入進行多樣化的格式下載顯示&#xff0c;還需要傳入一個下載總量&#xff0c;每次"下載以…

【算法】貪心算法

一、貪心算法基本思想 貪心算法總是作出在當前看來最好的選擇。也就是說貪心算法并不從 整體最優考慮&#xff0c;它所作出的選擇只是在某種意義上的局部最優選擇。 我們希望貪心算法得到的最終結果也是整體最優的。雖然貪心算法不 能對所有問題都得到整體最優解&#xff08;O…

MySQL事務與鎖機制詳解:確保數據一致性的關鍵【MySQL系列】

本文將系統講解 MySQL 中事務的四大特性、隔離級別與實現原理&#xff0c;深入拆解鎖機制的種類與應用場景&#xff0c;并結合典型死鎖案例進行分析&#xff0c;為你構建起應對復雜一致性問題的堅實基礎。 一、什么是事務&#xff1f; 事務&#xff08;Transaction&#xff09…

UE5 Mat HLSL - Load

特性Load()Sample()輸入類型整數索引&#xff08;int2/int3&#xff09;浮點 UV 采樣器狀態&#xff08;SamplerState&#xff09;數據獲取精確讀取指定位置的原始數據基于 UV 插值和過濾后的數據典型用途精確計算、非過濾訪問&#xff08;如物理模擬&#xff09;紋理貼圖渲染…

基于vue框架的獨居老人上門護理小程序的設計r322q(程序+源碼+數據庫+調試部署+開發環境)帶論文文檔1萬字以上,文末可獲取,系統界面在最后面。

系統程序文件列表 項目功能&#xff1a;用戶,護理人員,服務預約,服務評價,服務類別,護理項目,請假記錄 開題報告內容 基于Vue框架的獨居老人上門護理小程序的設計開題報告 一、研究背景與意義 &#xff08;一&#xff09;研究背景 隨著社會老齡化的加劇&#xff0c;獨居老…

鴻蒙如何引入crypto-js

import CryptoJS from ohos/crypto-js 報錯。 需要先安裝ohom&#xff1a;打開DevEco&#xff0c;點擊底部標簽組&#xff08;有Run, Build, Log等&#xff09;中的Terminal&#xff0c;在Terminal下執行&#xff1a; ohpm install 提示 install completed in 0s 119ms&…

【C++】入門基礎知識(1.5w字詳解)

本篇博客給大家帶來的是一些C基礎知識&#xff01;包含函數棧幀的詳解&#xff01; &#x1f41f;&#x1f41f;文章專欄&#xff1a;C &#x1f680;&#x1f680;若有問題評論區下討論&#xff0c;我會及時回答 ??歡迎大家點贊、收藏、分享&#xff01; 今日思想&#xff1…

二.MySQL庫的操作

一.創建數據庫create database 名稱; 字符集和校驗規則 一、字符集&#xff08;Character Set&#xff09; 表示數據庫中可以使用哪些字符。 例如&#xff1a;utf8 可以存儲包括中文在內的多種語言字符&#xff0c;gbk 更適合中文字符環境。 功能舉例控制支持哪些語言字符utf…

【Linux 學習計劃】-- 命令行參數 | 環境變量

目錄 命令行參數 環境變量 環境變量的本質是什么&#xff1f; 相關配置文件 修改環境變量的相關操作 代碼獲取env —— environ 內建命令 結語 命令行參數 試想一下&#xff0c;我們的main函數&#xff0c;也是一個函數&#xff0c;那么我們的main函數有沒有參數呢&am…

具有離散序列建模的統一多模態大語言模型【AnyGPT】

第1章 Instruction 在人工智能領域、多模態只語言模型的發展正迎來新的篇章。傳統的大型語言模型(LLM)在理解和生成人類語言方面展現出了卓越的能力&#xff0c;但這些能力通常局限于 文本處理。然而&#xff0c;現實世界是一個本質上多模態的環境&#xff0c;生物體通過視覺、…

git查看commit屬于那個tag

1. 快速確認commit原始分支及合入tag # git describe 213b4b3bbef2771f7a1b8166f6e6989442ca67c8 查看commit合入tag # git describe 213b4b3bbef2771f7a1b8166f6e6989442ca67c8 --all 查看commit原始分支 2.查看分支與master關系 # git show --all 0.5.67_0006 --stat 以縮…

day10機器學習的全流程

浙大疏錦行 1.讀取數據 import pandas as pd import pandas as pd #用于數據處理和分析&#xff0c;可處理表格數據。 import numpy as np #用于數值計算&#xff0c;提供了高效的數組操作。 import matplotlib.pyplot as plt #用于繪制各種類型的圖表# 設置中文字體…

基于對比學習的推薦系統開發方案,使用Python在PyCharm中實現

以下是一個基于對比學習的推薦系統開發方案,使用Python在PyCharm中實現。本文將詳細闡述技術原理、系統設計和完整代碼實現。 基于對比學習的推薦系統開發方案 一、技術背景與原理 1.1 對比學習核心思想 對比學習(Contrastive Learning)通過最大化正樣本相似度、最小化負…

2025山東CCPC題解

文章目錄 L - StellaD - Distributed SystemI - Square PuzzleE - Greatest Common DivisorG - Assembly Line L - Stella 題目來源&#xff1a;L - Stella 解題思路 簽到題&#xff0c;因為給出的字母不是按順序&#xff0c;可以存起來賦其值&#xff0c;然后在比較。 代碼…

某航參數逆向及設備指紋分析

文章目錄 1. 寫在前面2. 接口分析3. 加密分析4. 算法還原5. 設備指紋風控分析與繞過 【&#x1f3e0;作者主頁】&#xff1a;吳秋霖 【&#x1f4bc;作者介紹】&#xff1a;擅長爬蟲與JS加密逆向分析&#xff01;Python領域優質創作者、CSDN博客專家、阿里云博客專家、華為云享…

Python訓練營---Day41

DAY 41 簡單CNN 知識回顧 數據增強卷積神經網絡定義的寫法batch歸一化&#xff1a;調整一個批次的分布&#xff0c;常用與圖像數據特征圖&#xff1a;只有卷積操作輸出的才叫特征圖調度器&#xff1a;直接修改基礎學習率 卷積操作常見流程如下&#xff1a; 1. 輸入 → 卷積層 …

【Netty系列】Reactor 模式 2

目錄 流程圖說明 關鍵流程 以下是 Reactor 模式流程圖&#xff0c;結合 Netty 的主從多線程模型&#xff0c;幫助你直觀理解事件驅動和線程分工&#xff1a; 流程圖說明 Clients&#xff08;客戶端&#xff09; 多個客戶端&#xff08;Client 1~N&#xff09;向服務端發起連…

前端開發中 <> 符號解析問題全解:React、Vue 與 UniApp 場景分析與解決方案

前端開發中 <> 符號解析問題全解&#xff1a;React、Vue 與 UniApp 場景分析與解決方案 在前端開發中&#xff0c;<> 符號在 JSX/TSX 環境中常被錯誤解析為標簽而非比較運算符或泛型&#xff0c;導致語法錯誤和邏輯異常。本文全面解析該問題在不同框架中的表現及解…

【Web應用】 Java + Vue 前后端開發中的Cookie、Token 和 Swagger介紹

文章目錄 前言一、Cookie二、Token三、Swagger總結 前言 在現代的 web 開發中&#xff0c;前后端分離的架構越來越受到歡迎&#xff0c;Java 和 Vue 是這一架構中常用的技術棧。在這個過程中&#xff0c;Cookie、Token 和 Swagger 是三個非常重要的概念。本文將對這三個詞進行…

投稿Cover Letter怎么寫

Cover Letter控制在一頁比較好&#xff0c;簡短有力地推薦你的文章。 Dear Editors: Small objects detection in remote sensing field remains several challenges, including complex backgrounds, limited pixel representation, and dense object distribution, which c…