Linux多線程控制:深入理解與應用(萬字詳解!)

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?🎬慕斯主頁修仙—別有洞天

?? ????????????????????????????????????????? ???今日夜電波:どうして (feat. 野田愛実)

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 0:44━━━━━━?💟──────── 3:01
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ????🔄 ? ?? ? ? ? ?? ? ????

??????????????????????????????????????💗關注👍點贊🙌收藏您的每一次鼓勵都是對我莫大的支持😍


目錄

如何創建線程?

pthread_self()

如何終止線程?

通過return nullptr來線程終止

通過pthread_exit()來線程終止

通過pthread_cancel()來取消線程(先看后面的等待在回頭看這里)

線程等待

pthread_join()

pthread_detach()

一個小拓展

線程id詳解

pthread庫知識補充

clone()

系統調用問題

如何理解pthread庫來管理線程

前面提到的LWP為什么和pthread_self()獲得的不同?

線程局部存儲是啥?

__thread

線性局部存儲示例


如何創建線程?

????????在Linux中,可以使用POSIX線程庫(pthread)來創建線程pthread_create()函數是用于創建線程的函數。它定義在<pthread.h>頭文件中,其聲明如下:

#include <pthread.h>int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

? ? pthread_create()函數的參數具體含義如下:

  1. pthread_t *thread:這是一個指向pthread_t類型的指針,用于存儲新創建線程的ID。在調用pthread_create()時,可以傳遞一個pthread_t類型的指針變量,或者直接傳遞某個pthread_t類型變量的地址。
  2. const pthread_attr_t *attr:這個參數是一個指向pthread_attr_t類型的指針,用于設置線程的屬性。如果設置為NULL,則使用默認屬性創建線程。
  3. void *(*start_routine) (void *):這是一個函數指針,指向新線程將要執行的函數。這個函數通常被稱為線程函數,它應該接受一個void *類型的參數,并返回一個void *類型的值。
  4. void *arg:這是傳遞給線程函數的參數,可以是任意類型的指針。

? ? pthread_create()函數在成功時返回0,失敗時返回錯誤號。如果成功創建了線程,新線程將從start_routine指定的函數開始執行。

????????例子:

#include <iostream>
#include <string>
#include <functional>
#include <vector>
#include <time.h>
#include <unistd.h>
#include <pthread.h>// typedef std::function<void()> func_t;
using func_t = std::function<void()>;const int threadnum = 5;class ThreadData
{
public:ThreadData(const std::string &name, const uint64_t &ctime, func_t f): threadname(name), createtime(ctime), func(f){}public:std::string threadname;uint64_t createtime;func_t func;
};void Print()
{std::cout << "我是線程執行的大任務的一部分" << std::endl;
}// 新線程
void *ThreadRountine(void *args)
{int a = 10;ThreadData *td = static_cast<ThreadData *>(args);while (true){std::cout << "new thread"<< " thread name: " << td->threadname << " create time: " << td->createtime << std::endl;td->func();if(td->threadname == "thread-4"){std::cout << td->threadname << " 觸發了異常!!!!!" << std::endl;// a /= 0; // 故意制作異常}sleep(1);}
}
// 如何給線程傳參,如何創建多線程呢??? -- done
// 研究兩個問題: 1. 線程的健壯性問題 2. 觀察一下thread id// 獲取返回值
// 主線程
int main()
{std::vector<pthread_t> pthreads;for (size_t i = 0; i < threadnum; i++){char threadname[64];snprintf(threadname, sizeof(threadname), "%s-%lu", "thread", i);pthread_t tid;ThreadData *td = new ThreadData(threadname, (uint64_t)time(nullptr), Print);pthread_create(&tid, nullptr, ThreadRountine, td);pthreads.push_back(tid);sleep(1);}std::cout << "thread id: ";for(const auto &tid: pthreads){std::cout << tid << ",";}std::cout << std::endl;while (true){std::cout << "main thread" << std::endl;sleep(3);}
}

????????解析:如上代碼我們按照順序進行解讀:創建了一個ThreadData類,用于存儲線程的名字、創建時間以及函數指針,接下來的Print()函數就是我們主要要傳遞的給ThreadData對象的函數,接下來的ThreadRountine函數則是用于傳遞給pthread_create()函數中的void *(*start_routine) (void *)函數指針變量,由于給線程執行。需要注意的是其中有段代碼是故意制作除0錯誤的,用于驗證Linux使用多線程會造成健壯性降低的問題(只要其中一個線程出錯誤,那么其它線程也會收到影響,全部退出)。主函數中先是new出來ThreadData類型的對象,再將他通過pthread_create()函數中的*arg參數傳遞給新創建的線程。接著新線程執行對應的指令,主線程執行對應的指令。

pthread_self()

????????使用pthread_self()函數可以獲取當前線程的ID。下面是pthread_self()函數的聲明和用法示例:

#include <pthread.h>// 獲取當前線程ID
pthread_t current_thread_id = pthread_self();

????????在上面的示例中,pthread_self()函數被調用時,會返回當前線程的ID,并將其存儲在current_thread_id變量中。

??pthread_self()函數通常用于多線程程序中,當需要獲取當前線程的ID以進行一些特定的操作時使用。例如,可以使用當前線程的ID來區分不同線程的行為,或者將其作為參數傳遞給其他函數或數據結構。

????????需要注意的是:pthread_self()函數只能獲取當前線程的ID,不能用于獲取其他線程的ID。如果需要獲取其他線程的ID,可以使用pthread_equal()函數進行比較,或者將線程ID作為參數傳遞給其他函數進行處理。

????????在了解了這個函數后我們通過打印與ps -aL指令中的LWP做對比:

#include <iostream>
#include <pthread.h>
#include <unistd.h>void *ThreadRountine(void *args)
{usleep(1000);int a = 10;std::string name = static_cast<const char *>(args);while (true){std::cout << "i am a new thread, my name:" << name << " my id:" << pthread_self() << std::endl;sleep(1);}
}int main()
{pthread_t tid;pthread_create(&tid,nullptr,ThreadRountine,(void*)("thread -1"));while(true){std::cout << "i am man thread,"  << " my id:" << pthread_self() << std::endl;sleep(1);}return 0;
}

????????我們發現左半邊為一串很大的數字,與右邊完全不同:

????????接著將左半邊的數字轉換為16進制看看?我們發現他同地址很像,他們之間難道有什么關聯嗎?是的,因為thread id的本質就是一個地址!

如何終止線程?

通過return nullptr來線程終止

????????如下:在對應傳入的函數中返回nullptr,需要特別注意:我們能使用exit()函數來退出嗎?答案是不能!因為exit()是“進程終止”如果調用,整個進程都會退出!

void *ThreadRountine(void *args)
{usleep(1000);int a = 10;std::string name = static_cast<const char *>(args);while (a--){std::cout << "i am a new thread, my name:" << name << " my id:" << ToHex(pthread_self()) << std::endl;sleep(1);}return nullptr;
}

通過pthread_exit()來線程終止

? ? pthread_exit()是POSIX線程庫中的一個函數,用于終止當前線程的執行。

????????下面是pthread_exit()函數的聲明和用法示例:

#include <pthread.h>// 終止當前線程的執行
pthread_exit(nullptr);

????????在上面的示例中,pthread_exit()函數被調用時,會立即終止當前線程的執行,并返回到主線程或調用者。傳遞給pthread_exit()函數的參數是一個指向返回值的指針,這個返回值可以被其他線程通過pthread_join()函數獲取。如果不需要傳遞返回值,可以傳遞nullptr作為參數。

????????需要注意的是:pthread_exit()函數不會釋放線程所占用的資源,如堆棧、文件描述符等。這些資源的釋放需要程序員手動進行。

通過pthread_cancel()來取消線程(先看后面的等待在回頭看這里)

? ? pthread_cancel()函數用于取消一個線程的執行。它的原型如下:

#include <pthread.h>
int pthread_cancel(pthread_t thread);

參數說明:

  • thread:需要取消執行的線程ID。

返回值:

  • 成功時返回0;失敗時返回錯誤碼。

????????使用pthread_cancel()函數可以強制終止一個線程的執行,但需要注意的是:該函數并不會釋放線程所占用的資源,如堆棧、線程描述符等。因此,在線程被取消后,還需要調用其他函數來回收這些資源。

????????下面是一個使用pthread_cancel()函數的例子:

#include <iostream>
#include <pthread.h>
#include <unistd.h>void* thread_function(void* arg) {int i;for (i = 0; i < 10; i++) {std::cout << "Thread is running..." << std::endl;sleep(1);}return NULL;
}int main() {pthread_t thread;int result = pthread_create(&thread, NULL, thread_function, NULL);if (result != 0) {std::cerr << "Error creating thread!" << std::endl;return 1;}sleep(3); // 讓線程運行一段時間result = pthread_cancel(thread); // 取消線程執行if (result != 0) {std::cerr << "Error cancelling thread!" << std::endl;return 1;}// 等待線程結束,并回收資源void* retval;result = pthread_join(thread, &retval);if (result != 0) {std::cerr << "Error joining thread!" << std::endl;return 1;}std::cout << "Thread has been cancelled and joined successfully." << std::endl;return 0;
}

????????在這個例子中,我們創建了一個新線程,并在主線程中等待3秒鐘后調用pthread_cancel()函數來取消該線程的執行。然后,我們使用pthread_join()函數等待線程結束,并回收其資源。

????????需要注意的是:線程如果是被分離的,他是可以被取消的,但是不能被join。thread線程以不同的方法終止,通過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線程的終止狀態不感興趣,可以傳NULL給value_ ptr參數。

線程等待

????????我們都知道進程退出,他的PCB不會立即釋放,會處于僵尸狀態,進程要等待。那么線程也需要等待嗎?

????????是的,線程也是需要等待的!因為:線程退出沒有等待,會導致累充進程的僵尸問題我們可以通過pthread_join()來等待線程!

pthread_join()

? ? pthread_join()函數用于等待一個線程的結束,并回收其資源。它的原型如下:

#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);

參數說明:

  • thread:需要等待的線程ID。
  • retval:指向一個指針的指針,用于存儲被等待線程的返回值。如果不關心返回值,可以設置為nullptr。(為什么是void ** 類型呢?因為我們通過pthread_create傳入線程的函數的返回值是void *類型的返回值,我們用void **就可以接收這個函數的返回值,retval是一個輸出型的參數)

返回值:

  • 成功時返回0;失敗時返回錯誤碼。

????????例子1:

#include <iostream>
#include <pthread.h>
#include <unistd.h>void* print_hello(void* arg) {std::cout << "Hello from thread!" << std::endl;sleep(2);return NULL;
}int main() {pthread_t thread;void* retval;int ret;// 創建線程ret = pthread_create(&thread, NULL, print_hello, retval);if (ret != 0) {std::cerr << "Error creating thread!" << std::endl;return 1;}// 等待線程結束ret = pthread_join(thread, nullptr);if (ret != 0) {std::cerr << "Error joining thread!" << std::endl;return 1;}std::cout << "Thread joined successfully!" << std::endl;return 0;
}

????????在這個例子中,我們創建了一個新線程,該線程執行print_hello函數。然后,我們使用pthread_join()函數等待線程結束。當線程結束時,pthread_join()函數返回0,表示成功。

????????例子2:

#include <iostream>
#include <pthread.h>
#include <unistd.h>std::string ToHex(pthread_t tid)
{char id[64];snprintf(id, sizeof(id), "0x%lx", tid);return id;
}class ThreadReturn
{
public:ThreadReturn(pthread_t id, const std::string &info, int code): id_(id), info_(info), code_(code){}public:pthread_t id_;std::string info_;int code_;
};void *threadRoutine(void *arg)
{int cnt = 5;while (cnt--){std::cout << (const char *)arg << " is running..." << std::endl;sleep(1);}ThreadReturn *ret = new ThreadReturn(pthread_self(), "thread quit normal", 10);return ret;
}int main()
{// newpthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void *)("thread -1"));void *ret = nullptr;pthread_join(tid, &ret);ThreadReturn *r = static_cast<ThreadReturn *>(ret);std::cout << "main thread get new thread info:" << r->code_ << ", " << ToHex(r->id_) << ", " << r->info_ << std::endl;delete r;// mainwhile (true){std::cout << "i am man thread,"<< " my id:" << ToHex(pthread_self()) << std::endl;sleep(1);}return 0;
}

????????本例返回的是一個ThreadReturn *的值,運行效果如下:

pthread_detach()

????????線程默認的模式是joinable的,也就是說線程退出必須得等待,主線程必須是阻塞的等待新線程的。但是,我們也是可以設置為分離狀態的!即:我們可以設置為非阻塞狀態的,線程在退出時,對應的資源會被直接被回收。

? ? pthread_detach()函數用于將線程設置為分離狀態,從而在線程終止時自動回收其資源。它的原型如下:

#include <pthread.h>
int pthread_detach(pthread_t thread);

參數說明:

  • thread:需要設置為分離狀態的線程ID。

返回值:

  • 成功時返回0;失敗時返回錯誤碼。

需要注意的是:可以線程自己分離也可以由主線程分離。

????????例子:

#include <iostream>
#include <pthread.h>void* thread_function(void* arg) {// 線程執行的代碼return nullptr;
}int main() {pthread_t thread;int result = pthread_create(&thread, nullptr, thread_function, nullptr);if (result != 0) {std::cerr << "Error creating thread!" << std::endl;return 1;}// 分離線程result = pthread_detach(thread);if (result != 0) {std::cerr << "Error detaching thread!" << std::endl;return 1;}// 主線程的其他操作return 0;
}

????????在這個例子中,我們首先創建了一個新線程,然后立即將其分離。這樣,當線程結束時,它的資源會被自動回收,而不需要主線程顯式等待其結束。

一個小拓展

????????我們都知道進程退出會有退出碼,那線程退出會有退出碼嗎?答案是沒有!因為如果線程因為異常終止了,那么整個進程也會跟著終止,所以不需要退出碼!

線程id詳解

pthread庫知識補充

????????前面我們提到的對于線程控制的接口,實際上都不是系統直接提供的接口,而是原生線程庫pthread(系統會自帶這個庫)提供的接口。圖示如下:

????????通過pthread庫,可以對線程進行管理,我們通過“先描述,在組織”的原則會在pthread庫里面實現對應的結構體對象來描述,再通過一定的數據結構來組織。里面會包涵系統中“輕量級進程”的信息也會包涵用戶的“用戶級線程”信息。

?

clone()

????????clone()函數是Linux中的一個系統調用,用于創建新的執行線程或進程。它是fork()系統調用的泛化形式,具有更高的靈活性。實際上進程與線程的創建都是它的封裝。以下是對clone()函數的詳細解析:

1、函數原型

#include <sched.h>
int clone(int (*fn)(void *), void *child_stack, int flags, void *arg, ... /* pid_t *pid, struct user_desc *tls, pid_t *ctid */); 

2、參數說明

    • (*fn)(void *):子進程(或線程)執行時調用的函數。
    • child_stack:為子進程分配的堆棧指針。
    • flags:控制新進程與原進程之間的共享資源以及其它行為的標志位集合。
    • arg:傳給子進程的參數,一般為0。
    • ...:可選的附加參數,包括 pid_t *pid, struct user_desc *tls, pid_t *ctid

3、flags參數詳解

    • CLONE_PARENT:創建的子進程的父進程是調用者的父進程,使新進程與創建它的進程成為“兄弟”關系。
    • CLONE_FS:子進程與父進程共享相同的文件系統,包括root、當前目錄、umask等。

????????4、與fork()的區別:直接調用fork()等效于調用clone()時僅指定flags為SIGCHLD(共享信號句柄表)。fork()是Unix標準的復制進程的系統調用,而Linux實現了fork(), vfork(), clone三個系統調用。其中vfork()創造出來的是輕量級進程,也叫線程,是共享資源的進程。

????????5、使用場景:clone()通常用于實現多線程編程,因為它可以精細地控制哪些資源是共享的,哪些是私有的。這在多線程編程中是非常重要的,因為它允許創建高度定制的線程行為。

????????6、glibc封裝:從Linux 2.3.3開始,glibc的fork()封裝作為NPTL(Native POSIX Threads Library)線程實現的一部分。創建線程的函數pthread_create內部使用的也是clone函數。

系統調用問題

????????前面我們談到線程雖然有很多的共享資源,但是也要有獨立的屬性,其中最重要的是:1、上下文。2、棧。

????????其中棧是每個新線程會在pthread庫中維護的,而默認地址空間中的棧由主線程使用。前面在學習Linux動靜態庫的時候提到:加載庫會將庫加載到棧與堆之間的共享區中,pthread庫當然也是,而庫中的棧則是在其所屬進程的虛擬地址空間中分配

如何理解pthread庫來管理線程

????????如下這張圖想必大家都已經很熟悉了,我們在磁盤上存儲的pthread庫以及使用到pthread庫的可執行程序都會被加載到物理內存中,然后通過頁表映射到地址空間上。動態庫,也叫共享庫,只要在物理內存中映射了一次,之后都會被其他進程所共享。因此,pthread庫會管理整個系統中所創建的進程!理解上:線程庫是共享的,所以,內部要管理整個系統,多個用戶所啟動的所有線程。

????????如下為較為詳細的pthread理解:其中mmap區域就是共享區,而其中動態庫中struct_pthread可以理解為“先描述”,也可以理解為“TCB”。線程棧可以理解為一個指針,指向對應棧的地址。struct_pthread、線程局部存儲和線程棧可以理解為一個一個的塊每一個線程都有。與接下來的一個一個的塊被管理起來,可以理解為“在組織”。這些屬性都會被庫所維護。

????????如上struct_pthread會儲存對應的退出信息,而我們的pthread_join()函數接口就是讀取上面struct_pthread中的信息。如何找到的呢?我們是根據pthread_t tid來找到的,pthread_t tid就是線程屬性集合在庫中的地址!而其他pthread庫中的接口也就是根據這個就是對庫中的這些數據來進行維護的!

前面提到的LWP為什么和pthread_self()獲得的不同?

????????pthread_ create函數會產生一個線程ID,存放在第一個參數指向的地址中。該線程ID和前面說的線程ID不是一回事。

????????前面講的線程ID屬于進程調度的范疇。因為線程是輕量級進程,是操作系統調度器的最小單位,所以需要一個數值來唯一表示該線程。

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

?

線程局部存儲是啥?

__thread

? ? __thread是GCC提供的線程局部存儲(Thread-Local Storage, TLS)的關鍵字

? ? __thread用于聲明線程局部變量,這意味著每個線程都會有該變量的一個獨立實例。不同線程中的__thread變量互不干擾,各自保有自己獨立的值,這對于多線程編程中需要為每個線程保持獨立狀態的場景非常有用。

????????具體來說,__thread關鍵字的使用場景和限制包括:

  • 應用場景:適合修飾那些具有全局性質且值可能會發生變化的變量,但又不需要像全局變量那樣進行保護的情況。
  • 效率優勢__thread變量的存取效率可以與全局變量相媲美,這使得它在性能上非常有吸引力。
  • 使用限制:只能修飾POD(Plain Old Data)類型,即那些不含有自定義構造、拷貝、賦值、析構函數的簡單數據類型。因為__thread無法自動調用類的構造和析構函數,所以它不能用來修飾類類型的變量。
  • 作用范圍:可以用來修飾全局變量和函數內的靜態變量,但不能修飾函數的局部變量或類的普通成員變量。
  • 初始化限制__thread變量的值只能初始化為編譯器常量。

????????總的來說,在多線程編程中,__thread提供了一種方便高效的方式來為每個線程創建獨立的變量副本,從而避免了共享數據帶來的競爭條件和同步問題。

線性局部存儲示例

????????如下為正常的全局變量在多線程情況下的示例:也印證了多線程共享資源的特性!

#include <iostream>
#include <string>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/syscall.h>
#include <pthread.h>using namespace std;int g_val = 100; // 全局變量,本身就是被所有線程共享的void *threadRoutine(void *args)
{std::string name = static_cast<const char *>(args);sleep(1);while (true){sleep(1);std::cout << name << ", g_val: " << g_val << " ,&g_val: " << &g_val << "\n"<< std::endl;g_val++;}return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void *)"thread1");while (true){sleep(1);std::cout << "main thread, g_val: " << g_val << " ,&g_val: " << &g_val << "\n"<< std::endl;}pthread_join(tid, nullptr);return 0;
}

????????如下為使用__thread的示例:可以發現本來共享的全局變量變成了線性局部的變量,值和地址都會變化!其中拓展了對于獲取LWP通過調用系統調用 SYS_gettid 獲取當前線程的 TID

#include <iostream>
#include <string>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/syscall.h>
#include <pthread.h>using namespace std;// int g_val = 100; // 全局變量,本身就是被所有線程共享的
__thread int g_val = 100; // 線程的局部存儲!有什么用?有什么坑?__thread pid_t lwp = 0;// __thread std::string threadname;pid_t gettid() {return syscall(SYS_gettid);
}void *threadRoutine(void *args)
{std::string name = static_cast<const char *>(args);lwp = gettid(); // 調用系統調用 SYS_gettid 獲取當前線程的 TIDwhile (true){sleep(1);std::cout << name << ", g_val: " << g_val << " ,&g_val: " << &g_val << "\n"<< std::endl;std::cout <<"new thread: " << lwp << std::endl;g_val++;}return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, (void *)"thread1");lwp = gettid(); // 調用系統調用 SYS_gettid 獲取當前線程的 TIDstd::cout <<"main thread: " << lwp << std::endl;while (true){sleep(1);std::cout << "main thread, g_val: " << g_val << " ,&g_val: " << &g_val << "\n"<< std::endl;}pthread_join(tid, nullptr);
}


???????????????????????感謝你耐心的看到這里?( ′・?・` )比心,如有哪里有錯誤請踢一腳作者o(╥﹏╥)o!?

????????????????????????????????? ? ? ?

????????????????????????????????????????????????????????????????????????給個三連再走嘛~??

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

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

相關文章

6.2 指標的應用與設計(12%)

1、指標的作用 用簡約的匯總數據量化業務強弱。 2、指標的理解 特點&#xff1a; &#xff08;1&#xff09;指標是游離態的&#xff0c;無法單獨實現數據統計 eg&#xff1a;總銷售額、總銷售量 &#xff08;2&#xff09;需與統計維度結合&#xff0c;明確統計指標的對…

帕累托優化基本概念,如何系統學習?

帕累托優化&#xff0c;也稱為帕累托改善或帕累托改進&#xff0c;是以意大利經濟學家帕累托&#xff08;Vilfredo Pareto&#xff09;命名的。它的基本概念是在沒有使任何人境況變壞的前提下&#xff0c;使得至少一個人變得更好。帕累托最優是指沒有進行帕累托改進余地的狀態&…

047 內部類

成員內部類用法 /*** 成員內部類** author Admin*/ public class OuterClass {public void say(){System.out.println("這是類的方法");}class InnerClass{public void say(){System.out.println("這是成員內部類的方法");}}public static void main(Stri…

(二)邏輯回歸與交叉熵--九五小龐

什么是邏輯回歸 線性回歸預測的是一個連續值&#xff0c;邏輯回歸給出的“是”和“否”的回答 Singmoid sigmoid函數是一個概率分布函數&#xff0c;給定某個輸入&#xff0c;它將輸出為一個概率值 邏輯回歸損失函數 平方差所懲罰的是與損失為同一數量級的情形&#xff0…

Springboot企業級開發--1.開發入門

目錄 目錄 一.Spring Boot的主要特點和優勢包括&#xff1a; 二.Spring Boot的核心功能可以歸納為以下幾點&#xff1a; 三.Springboot是如何解決問題&#xff1f; Spring Boot 是一個開源的Java框架&#xff0c;其設計目標是為了簡化新Spring應用的初始搭建以及開發過程。…

SandBox中的JavaAgent技術

8.1 JavaAgent Java Agent 是一種強大的技術&#xff0c;在運行時動態修改已加載類的字節碼&#xff0c;為應用程序注入額外的功能和行為。 JDK 1.5 支持靜態 Instrumentation&#xff0c;基本的思路是在 JVM 啟動的時候添加一個代理&#xff08;javaagent&#xff09;&#…

基于阿里云OSS上傳圖片實戰案例

一、案例描述 基于Springboot框架實現一個上傳圖片到阿里云服務端保存的小案例。 二、準備工作 基于Springboot免費搭載輕量級阿里云OSS數據存儲庫&#xff08;將本地文本、照片、視頻、音頻等上傳云服務保存&#xff09;-CSDN博客 三、代碼 新建這兩個類&#xff1a;一個…

Golang函數make介紹和用法

1.介紹 golang分配內存主要有內置函數new和make 相同點: 他們的第一個參數都是一個類型而不是一個值 不同點: new可分配任意類型的數據make只能為slice, map, channel分配內存new返回的是指針make返回類型的是引用而不是指針,并且返回的值也依賴于具體傳入的類型, 這種不同點的…

C++原子操作

8.3.5 原子操作 在同一時刻只有唯一的線程對這個資源進行訪問。這有點類似互斥對象對共享資源的訪問的保護&#xff0c;但是原子操作更加接近底層&#xff0c;因而效率更高。 &#xff08;1&#xff09;pthread #include <stdatomic.h> atomic_int atomicVariable; at…

Python如何從SQL Server存取數據?

在Python中&#xff0c;你可以使用各種庫來連接和操作 SQL Server 數據庫。一種常用的庫是pyodbc&#xff0c;它是一個用于連接到各種數據庫的開源 Python 庫&#xff0c;包括 SQL Server。以下是連接到 SQL Server 并存取數據的基本步驟&#xff1a; 1、安裝 pyodbc 庫&#…

LANA: A Language-Capable Navigator for Instruction Following and Generation

摘要 最近&#xff0c;視覺語言導航&#xff08;VLN&#xff09;——要求機器人代理遵循導航指令——已經取得了巨大的進步。然而&#xff0c;現有文獻最強調將指令解釋為行動&#xff0c;只提供“愚蠢”的尋路代理。在本文中&#xff0c;我們設計了 LANA&#xff0c;一種支持…

【C++ 異常處理】

C 異常處理 ■ C 異常處理簡介■ throw (拋出異常)■ catch (捕獲異常)■ try&#xff08;&#xff09;■ C 標準的異常 ■ C 異常處理簡介 C 異常處理涉及到三個關鍵字&#xff1a;try、catch、throw。 屬性描述throw當問題出現時&#xff0c;程序會拋出一個異常。這是通過使…

【LeetCode-1143】最長公共子序列(動歸)

目錄 題目描述 解法1&#xff1a;動態規劃 代碼實現 題目鏈接 題目描述 給定兩個字符串 text1 和 text2&#xff0c;返回這兩個字符串的最長公共子序列的長度。 一個字符串的 子序列 是指這樣一個新的字符串&#xff1a;它是由原字符串在不改變字符的相對順序的情況下刪除…

Linux系統這些壓測工具,你用過嗎?

作為一名運維人員,你是否遇到過這種場景?需要用工具測試系統cpu或內存占用高來觸發告警,或者通過壓測測試服務的并發能力。作為運維工程師,也可以通過這些命令復現故障場景。那么通過本文可以讓你掌握常用的測試命令和工具。 更多技術博客,請關注微信公眾號:運維之美 一、…

LIDAR2Camera 手動標定

參考&#xff1a;搞懂了&#xff01;原來激光雷達和相機的內外參是這樣標定的_嗶哩嗶哩_bilibili 代碼下載&#xff1a;SensorsCalibration/lidar2camera at master PJLab-ADG/SensorsCalibration (github.com)

社區店選址評估:利用大數據選址的技巧與策略

在當今數字化的時代&#xff0c;利用大數據進行社區店選址評估已成為一種高效、科學的方法。作為一名開鮮奶吧5年的創業者&#xff0c;我將分享一些利用大數據選址的技巧與策略&#xff0c;幫助你找到最適合的店鋪位置。 1、確定目標商圈 在選址之前&#xff0c;首先要明確自己…

涉及主頁面內嵌iframe中的列表數據的保存

場景&#xff1a;主表 : 附表 1 : m&#xff0c;同一個頁面&#xff0c;共同使用一個保存按鈕進行兩個表的數據保存&#xff0c;頁面中間有個查詢按鈕&#xff0c;可以對子iframe頁面的內容進行刷新 流程項目頁面內嵌了個子iframe&#xff0c;項目頁面表單數據提交保存是一個…

爬蟲的一些小技巧總結

一、在爬蟲中&#xff0c;爬取的數據類型如下 1.document:返回的是一個HTML文檔 2.png:無損的圖片&#xff0c;jpg:壓縮后的圖片,wbep:有損壓縮&#xff0c;比png差&#xff0c;比jpg好 3.avgxml圖像編碼字符串 4.script:腳本文件&#xff0c;依據一定格式編寫的可執行的文…

【大廠AI課學習筆記NO.58】(11)混淆矩陣

混淆矩陣&#xff08;confusion matrix&#xff09;—— 混淆矩陣&#xff08;Confusion Matrix&#xff09;是人工智能領域&#xff0c;特別是在機器學習和深度學習中&#xff0c;用于衡量分類模型性能的重要工具。它通過統計分類模型的真實分類與預測分類之間的結果&#xf…

【python debug】python常見編譯問題解決方法_2

序言 記錄python使用過程中碰到的一些問題及其解決方法上一篇&#xff1a;python常見編譯問題解決方法_1 1. PermissionError: [Errno 13] Permission denied: ‘/lostfound’ 修改前&#xff1a; 修改后&#xff08;解決&#xff09;&#xff1a; 此外&#xff0c;可能文件夾…