【Linux系統篇】:Linux線程控制基礎---線程的創建,等待與終止

?感謝您閱讀本篇文章,文章內容是個人學習筆記的整理,如果哪里有誤的話還請您指正噢?
? 個人主頁:余輝zmh–CSDN博客
? 文章所屬專欄:Linux篇–CSDN博客

在這里插入圖片描述

文章目錄

  • 一.線程創建
  • 二.線程等待
  • 三.線程終止
  • 四.擴展內容
    • 1.重談`pthread_create`函數
    • 2.C++11線程庫
    • 3.線程棧結構
    • 4.線程局部存儲
    • 5.分離線程

POSIX線程庫

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

一.線程創建

線程創建函數

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 <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;// 新線程的執行函數
void *pthreadRoution(void *args){while(true){cout << "new thread, pid: " << getpid() << endl;sleep(1);}
}int main(){pthread_t tid;// 主線程創建一個新線程pthread_create(&tid, nullptr, pthreadRoution, nullptr);// 主線程while(true){cout << "main thread, pid: " << getpid() << endl;sleep(1);}return 0;
}

在這里插入圖片描述

結合創建的線程重新認識一下線程的相關概念:

1.任何一個線程被干掉,其余線程包括整個進程都會被干掉,所以這就是為什么線程的健壯性很差

在這里插入圖片描述

2.在多線程情況下,一個方法可以被多個執行流同時執行,這種情況就是show函數被重入了

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <string>
using namespace std;// show函數方法
void show(const string &name){cout << name << "say# " << "hello thread" << endl;
}// 新線程的執行函數
void *pthreadRoution(void *args){while(true){//cout << "new thread, pid: " << getpid() << endl;show("[new thread]");sleep(2);}
}int main(){pthread_t tid;// 主線程創建一個新線程pthread_create(&tid, nullptr, pthreadRoution, nullptr);// 主線程while(true){//cout << "main thread, pid: " << getpid() << endl;show("[main thread]");sleep(2);}return 0;
}

在這里插入圖片描述

3.未初始化和已初始化的全局變量在所有線程中是共享的

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <string>
using namespace std;int g_val = 100;
// 新線程的執行函數
void *pthreadRoution(void *args){while(true){printf("new thread pid: %d, g_val: %d, &g_val: 0x%p\n", getpid(), g_val, &g_val);sleep(2);}
}int main(){pthread_t tid;// 主線程創建一個新線程pthread_create(&tid, nullptr, pthreadRoution, nullptr);// 主線程while(true){printf("main thread pid: %d, g_val: %d, &g_val: 0x%p\n", getpid(), g_val, &g_val);sleep(2);g_val++;}return 0;
}

在這里插入圖片描述

根據上面的例子可以發現,線程之前想要通信會變得非常簡單,因為線程之間天然的就具有共享資源

二.線程等待

一般而言,主線程一定會是最后退出的,因為其他線程是由主線程創建的,主線程就要對創建出來的新線程做管理,和父進程等待回收子進程一樣,主線程也要等待其他線程進行回收,否則就會造成類似于僵尸進程的問題,比如內存泄漏;同理,主線程創建新線程肯定是要執行一些任務,最后新線程的執行情況也是要返回給主線程的。

所以線程等待和進程等待同理,兩個目的:

1.防止新線程造成內存泄露(主要目的)

2.如果需要,主線程也可以獲取新線程的執行結果

線程等待函數

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

參數

  • thread:線程ID
  • value_prt:二級指針,指向一個指針的地址,輸出型參數,用來獲取線程的返回值;如果不關心返回值,可以直接設置為空指針

返回值:成功返回0;失敗返回錯誤碼。

調用該函數的線程將掛起等待,直到ID為thread的線程終止;一般都是主線程調用,所以主線程等待時默認是阻塞式等待

測試代碼:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <string>
using namespace std;int g_val = 100;
// 新線程的執行函數
void *pthreadRoution(void *args){int cnt = 5;while (true){printf("new thread pid: %d, g_val: %d, &g_val: 0x%p\n", getpid(), g_val, &g_val);sleep(2);cnt--;if (cnt == 0){break;}}return (void *)100;
}int main(){pthread_t tid;// 主線程創建一個新線程pthread_create(&tid, nullptr, pthreadRoution, nullptr);void *retval;pthread_join(tid, &retval);cout << "main thread quit ..., ret: " << (long long int)retval << endl;return 0;
}

在這里插入圖片描述

為什么線程等待時不用考慮異常呢?

因為根本做不到,一旦其中一個線程出現異常,整個進程也就直接終止退出了。異常問題是由進程考慮的,線程只需要考慮正常情況即可。

三.線程終止

線程終止時直接使用return語句返回是其中一種方法,除了這個還用其他方法,

先測試使用exit終止線程:

//線程等待的測試代碼中使用exit終止退出
void *pthreadRoution(void *args){int cnt = 5;while (true){printf("new thread pid: %d, g_val: %d, &g_val: 0x%p\n", getpid(), g_val, &g_val);sleep(1);cnt--;if (cnt == 0){break;}}exit(11);//return (void *)100;
}

在這里插入圖片描述

最后的結果現象就是,新線程終止退出后,主線程并沒有回收新線程,這是因為調用exit函數使整個進程都終止退出了。

任何一個線程調用exit,都表示整個進程終止;exit是用來終止進程的,不能用來終止線程。

線程終止可以使用線程庫中的pthread_exit函數

線程終止函數

void pthread_exit(void *value_ptr);

參數value_ptr:指向線程終止時返回值的地址;注意,要返回的指針不能指向一個局部變量

返回值:無返回值,線程結束的時候無法返回到他的調用者。

測試代碼:

void *pthreadRoution(void *args){int cnt = 5;while (true){printf("new thread pid: %d, g_val: %d, &g_val: 0x%p\n", getpid(), g_val, &g_val);sleep(1);cnt--;if (cnt == 0){break;}}pthread_exit((void *)100);// exit(11);// return (void *)100;
}

在這里插入圖片描述

如果主線程先退出,創建出的新線程后退出,最后的現象就是一旦主線程,其余的線程都會退出,也就是整個進程退出

int main(){pthread_t tid;// 主線程創建一個新線程pthread_create(&tid, nullptr, pthreadRoution, nullptr);// 主線程一秒后退出sleep(1);return 0;void *retval;pthread_join(tid, &retval);cout << "main thread quit ..., ret: " << (long long int)retval << endl;return 0;
}

在這里插入圖片描述

因為主線程是在main函數中,在main函數return 相當于調用exit函數終止整個進程;

需要注意的是,使用pthread_exit或者return這兩種方式來終止線程時,返回的指針所指向的內存單元必須是全局的或者是在堆區上分配的,不能在線程當前執行的函數的棧上分配,因為一旦線程結束函數調用時,棧上分配的空間就會自動釋放

一個線程終止退出,除了上面的的returnpthread_exit函數兩種方式以外,還有一種退出方式:線程取消,調用pthread_cancel函數

int pthread_cancel(pthread_t thread);

參數thread:線程ID

返回值:成功返回0;失敗返回錯誤碼

線程取消是由主線程調用pthread_cancel函數像目標線程發送一個終止請求,測試代碼:

int main(){pthread_t tid;// 主線程創建一個新線程pthread_create(&tid, nullptr, pthreadRoution, nullptr);sleep(1);pthread_cancel(tid);void *retval;pthread_join(tid, &retval);cout << "main thread quit ..., ret: " << (long long int)retval << endl;return 0;
}

在這里插入圖片描述

線程取消后,退出結果就會設置成一個宏PTHREAD_CANCELED(表示-1),線程等待就會獲取到退出結果-1。

總結:如果需要只終止某個線程而不終止整個進程,有三種方式

1.線程執行的函數return;主線程不適用。

2.線程調用pthread_exit終止自己。

3.一個線程可以調用pthread_cancel終止同一進程中的另一個線程。

四.擴展內容

1.重談pthread_create函數

前面提到過pthread_create函數的第四個參數和返回值都是void*類型,采用該類型主要是通過泛型的思想,適配任何指針類型。

其中pthread_create函數的第四個參數和返回值除了傳遞普通的內置類型(比如整形,字符串型等),還可以傳遞自定義類型(類和對象)

通過一段代碼來測試:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <string>
using namespace std;class Request{
public:Request(int start, int end, const string &threadname):_start(start),_end(end),_threadname(threadname){}
public:int _start;int _end;string _threadname;
};class Response{
public:Response(int result, int exitcode):_result(result),_exitcode(exitcode){}
public:int _result;int _exitcode;
};void *sumcount(void *args){Request *rq = static_cast<Request *>(args);Response *rsp = new Response(0, 0);for (int i = rq->_start; i <= rq->_end;i++){rsp->_result += i;}delete rq;pthread_exit(static_cast<void *>(rsp));
}int main(){pthread_t tid;Request *rq = new Request(1, 100, "thread 1");// 主線程創建一個新線程pthread_create(&tid, nullptr, sumcount, rq);// 主線程回收新線程void *retval;pthread_join(tid, &retval);Response *rsp = static_cast<Response *>(retval);cout << "rsp->result: " << rsp->_result << " rsp->exitcode: " << rsp->_exitcode << endl;delete rsp;return 0;
}

在這里插入圖片描述

在上面的測試代碼中,傳遞指針時,將自定義類型(rq)的指針強制轉換為void*;然后在線程函數中,將void*強制轉換為原始類型。

自定義類型的對象都是通過malloc或者new在堆上開辟空間的。

而堆空間也是被所有線程共享的,但是共享堆空間不等于自動共享數據

  • 堆空間的共享性:所有線程共享同一進程的堆內存區域,但堆上的數據必須通過指針來定位,需要明確直到其地址才能訪問;
  • 數據的隔離性:雖然所有線程共享堆空間,但是線程之間默認不知道對方在堆中創建了哪些數據對象。

所以給線程的執行函數傳參時,也可以傳入自定義類型的對象的指針。

2.C++11線程庫

這里先簡單的了解即可,之后學C++11時會再重點講解更詳細的使用

C++11的線程庫簡單使用:

#include <iostream>
#include <unistd.h>
#include <string>
#include <thread>
using namespace std;void threadrun(){while(true){cout << "I am a new thread for C++" << endl;sleep(1);}
}int main(){// C++11的線程庫thread t1(threadrun);t1.join();return 0;
}

C++11里的線程庫本質上還是封裝的原生線程庫,所以使用C++11的線程庫編譯時還是需要帶上-lpthread選項,此外還要帶上-std=c++11選項

在這里插入圖片描述

3.線程棧結構

每個線程在運行時都要有自己獨立的棧結構,因為每一個線程都會有自己的調用鏈,也就注定了每一個線程都要有一個調用鏈對應的獨立棧幀結構,這個棧結構會保存任意一個執行流在運行過程中所有的臨時變量,比如壓棧時傳參形成的臨時變量;返回時的返回值以及地址,包括線程自己在函數中定義的臨時變量,所以每個線程都要有自己獨立的棧結構。

其中主線程直接使用地址空間中提供的的棧結構,這就是系統真正意義上的進程

除了主線程外,其他線程的獨立棧結構,都在共享區,具體來說是在pthread庫中,每一個線程都有一個線程控制塊tcb(由線程庫維護),線程的棧結構就存儲在tcb中,而tcb的起始地址就是線程的tid

上面講解線程創建時函數的第一個參數線程ID,就是這個線程的tid地址。后續線程的所有操作都是根據這個線程ID來實現的。

線程庫中還提供了pthread_self函數,可以獲取線程自身的ID:

pthread_t pthread_self(void);

在這里插入圖片描述

多線程測試棧區獨立:

#include <iostream>
#include <unistd.h>
#include <vector>
#include <string>
#include <pthread.h>
using namespace std;#define NUM 3class threadData{
public:threadData(int number):threadname("thread-"+to_string(number)){}
public:string threadname;
};string toHex(pthread_t tid){char buffer[128];snprintf(buffer, sizeof(buffer), "0x%x", tid);return buffer;
}// 所有線程都會調用這個函數
void *threadRoutine(void *args){threadData *td = static_cast<threadData *>(args);int i = 0;// 每個線程都創建一個test_i變量int test_i = 0;    while (i < 5){cout << "pid: " << getpid() << ", tid: " << toHex(pthread_self())<< ", threadname: " << td->threadname<< ", test_i: " << test_i << ", &test_i: " << toHex((pthread_t)&test_i) << endl;sleep(1);i++;test_i++;}delete td;return nullptr;
}int main(){// 創建多線程vector<pthread_t> tids;for (int i = 0; i < NUM; i++){pthread_t tid;threadData *td = new threadData(i);  // 在堆區創建pthread_create(&tid, nullptr, threadRoutine, td);tids.push_back(tid);sleep(1);}for (int i = 0; i < tids.size(); i++){pthread_join(tids[i], nullptr);}return 0;
}

在這里插入圖片描述

根據上面的測試就可以證明,每個線程都有自己的獨立棧結構

如果某個線程想訪問另一個線程棧區上的變量,也是可以實現的:

#include <iostream>
#include <unistd.h>
#include <vector>
#include <string>
#include <pthread.h>
using namespace std;#define NUM 3int *p = nullptr;class threadData{
public:threadData(int number):threadname("thread-"+to_string(number)){}
public:string threadname;
};string toHex(pthread_t tid){char buffer[128];snprintf(buffer, sizeof(buffer), "0x%x", tid);return buffer;
}// 所有線程都會調用這個函數
void *threadRoutine(void *args){threadData *td = static_cast<threadData *>(args);int i = 0;int test_i = 0;    // 該變量在每個線程的棧區創建if(td->threadname=="thread-2"){p = &test_i;}while (i < 5){cout << "pid: " << getpid() << ", tid: " << toHex(pthread_self())<< ", threadname: " << td->threadname<< ", test_i: " << test_i << ", &test_i: " << &test_i << endl;sleep(1);i++;test_i++;}delete td;return nullptr;
}int main(){// 創建多線程vector<pthread_t> tids;for (int i = 0; i < NUM; i++){pthread_t tid;threadData *td = new threadData(i);  // 在堆區創建pthread_create(&tid, nullptr, threadRoutine, td);tids.push_back(tid);sleep(1);}sleep(1);cout << "main thread get a local value,val: " << *p << ", &val: " << p << endl;for (int i = 0; i < tids.size(); i++){pthread_join(tids[i], nullptr);}return 0;
}

在這里插入圖片描述

所有線程本來就共享代碼區,全局變量區,堆區,共享區等,而對于線程獨立的棧結構上的數據,也是可以被其他線程看到并訪問的,所以線程和線程之間,幾乎沒有秘密。

4.線程局部存儲

全局變量可以被所有線程看到并訪問的

#include <iostream>
#include <unistd.h>
#include <vector>
#include <string>
#include <pthread.h>
using namespace std;#define NUM 3//int *p = nullptr;
int g_val = 0;class threadData{
public:threadData(int number):threadname("thread-"+to_string(number)){}
public:string threadname;
};string toHex(pthread_t tid){char buffer[128];snprintf(buffer, sizeof(buffer), "0x%x", tid);return buffer;
}// 所有線程都會調用這個函數
void *threadRoutine(void *args){threadData *td = static_cast<threadData *>(args);int i = 0;//int test_i = 0;    // 該變量在每個線程的棧區創建// if(td->threadname=="thread-2"){//     p = &test_i;// }while (i < 5){cout << "pid: " << getpid() << ", tid: " << toHex(pthread_self())<< ", threadname: " << td->threadname<< ", g_val: " << g_val << ", &g_val: " << &g_val << endl;sleep(1);i++;//test_i++;g_val++;}delete td;return nullptr;
}int main(){// 創建多線程vector<pthread_t> tids;for (int i = 0; i < NUM; i++){pthread_t tid;threadData *td = new threadData(i);  // 在堆區創建pthread_create(&tid, nullptr, threadRoutine, td);tids.push_back(tid);sleep(1);}sleep(1);//cout << "main thread get a local value,val: " << *p << ", &val: " << p << endl;for (int i = 0; i < tids.size(); i++){pthread_join(tids[i], nullptr);}return 0;
}

在這里插入圖片描述

在上面的測試代碼中,g_val是一個全局變量,被所有線程共享訪問,所以這個g_val其實就是一個共享資源。

如果線程想要一個私有的全局變量,如何實現?

直接在定義的全局變量之前加__thread即可:

__thread int g_val=0;

在這里插入圖片描述

每一個線程都訪問的是同一個全局變量,但是每一個全局變量對于每一個線程來講,都是各自私有一份,這種技術就是線程的局部存儲

__thread不是C語言或C++的關鍵字,而是編譯器提供的一個編譯選項,編譯的時候會默認將這個g_val變量給每一個線程在獨立的棧結構上申請一份空間。

注意點:

__thread定義線程的局部存儲變量時只能用來定義內置類型,不能用來修飾自定義類型:

在這里插入圖片描述

5.分離線程

  • 默認情況下,新創建的線程是joinable的,線程退出后,需要對其進行pthread_join操作,否則無法釋放資源,從而造成內存泄漏問題;
  • 但是如果線程等待時,并不關心線程的返回值,此時join就是一種負擔,這個時候,我們可以使用pthread_detach函數分離線程,告訴系統當線程退出時,自動釋放線程資源。
int pthread_detach(pthread_t thread);

可以是線程組內其他線程對目標線程進行分離(比如主線程使某個線程分離),也可以是線程自己分離(在線程的執行函數中調用pthread_detach(pthread_self()))。

注意:joinable和分離是沖突的,一個線程不能既是joinable又是分離的

pthread_joinpthread_detach兩個函數不能對同一個線程使用。

以上就是關于線程控制部分的講解,如果哪里有錯的話,可以在評論區指正,也歡迎大家一起討論學習,如果對你的學習有幫助的話,點點贊關注支持一下吧!!!

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

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

相關文章

More Effective C++學習筆記

條款1 指針與引用的區別 條款2 盡量使用C風格的類型轉換 條款3 不要對數組使用多態 條款4 避免無用的缺省構造函數 條款5 謹慎定義類型轉換函數 條款6 自增(increment)、自減(decrement)操作符前綴形式與后綴形式的區別 條款7 不要重載“&&”,“||”, 或“,” 條款8 理…

先知AIGC超級工場,撬動運營效率新杠桿

北京先智先行科技有限公司&#xff0c;作為行業內的重要參與者&#xff0c;擁有“先知大模型”、“先行AI商學院”以及“先知AIGC超級工場”這三款旗艦產品。這些產品在不同領域發揮著關鍵作用&#xff0c;尤其是先知AIGC超級工場&#xff0c;正悄然改變著內容創作與產品推廣的…

十一歲少年葉珉雪用藝術點亮公益之路 個人原創公益演唱會傳遞大愛與擔當

4月29日晚&#xff0c;"韶華映雪益路同行"葉珉雪個人原創公益演唱會在廣東碧桂園學校歌劇院圓滿落幕。 這場由該校美育成果澆灌出的藝術盛宴&#xff0c;生動詮釋了廣東碧桂園學校育人理念。11歲的葉珉雪以超越年齡的藝術掌控力&#xff0c;呈現了一場融合歌唱、舞蹈…

【深度學習基礎】:VGG實戰篇(圖像風格遷移)

文章目錄 前言style transfer原理原理解析損失函數 style transfer代碼效果圖 fast style transfer 代碼效果圖 前言 本篇來帶大家看看VGG的實戰篇&#xff0c;這次來帶大家看看計算機視覺中一個有趣的小任務&#xff0c;圖像風格遷移。 可運行代碼位于&#xff1a; Style_tr…

python爬蟲基礎:requests庫詳解與案例

1.Requests模塊的使用 requests模塊的介紹與安裝 作用&#xff1a;發送網絡請求&#xff0c;返回響應數據。 中文文檔&#xff1a;https://requests.readthedocs.io/projects/cn/zh_CN/latest/ 對于爬蟲任務&#xff0c;使用 requests模塊基本能夠解決絕大部分的數據抓取的…

Spring 容器相關的核心注解?

以下是 Spring 容器中用于 ??Bean 管理、依賴注入、配置控制?? 的關鍵注解&#xff0c;按功能分類說明&#xff1a; ??1. Bean 聲明與注冊?? 注解作用示例??Component??通用注解&#xff0c;標記一個類為 Spring Bean&#xff08;自動掃描注冊&#xff09; Compo…

C與指針5——字符串合集

常用函數 1、拷貝、長度、比較 size_t strlen();\\返回無符號整形 char* strcpy();char* strncpy();\\拷貝 int strcmp();int strncmp();\\比較 char* strcat();char* strncat();\\連接2、查找 char* strchr(const char * st,int ch);\\找字符第一次出現的位置 char* strrch…

論軟件需求管理

目錄 摘要&#xff08;300~330字&#xff09; 正文&#xff08;2000~2500字&#xff0c;2200字為宜&#xff09; 背景介紹&#xff08;500字做左右&#xff09; 論點論據&#xff08;1500字做左右&#xff09; 收尾&#xff08;200字左右&#xff09; 注&#xff1a;本篇論…

[特殊字符] 如何在比賽前調整到最佳狀態:科學與策略結合的優化指

&#x1f9e0; 概述 在競技體育中&#xff0c;賽前狀態的調整對比賽結果起著決定性作用。所謂“最佳狀態”&#xff0c;不僅指生理上的巔峰表現&#xff0c;更包括心理、認知、營養和恢復等多方面的協同優化。本文結合運動科學、心理學和營養學的研究成果&#xff0c;探討賽前…

一種實波束前視掃描雷達目標二維定位方法——論文閱讀

一種實波束前視掃描雷達目標二維定位方法 1. 專利的研究目標與實際問題意義2. 專利提出的新方法、模型與公式2.1 運動平臺幾何建模與回波信號構建2.1.1 距離歷史建模2.1.2 回波信號模型2.2 距離向運動補償技術2.2.1 匹配濾波與距離壓縮2.3 加權最小二乘目標函數2.3.1 方位向信號…

基于 Spring Boot 瑞吉外賣系統開發(八)

基于 Spring Boot 瑞吉外賣系統開發&#xff08;八&#xff09; 自動填充公共字段 MyBatis-Plus公共字段自動填充&#xff0c;也就是在插入或者更新的時候為指定字段賦予指定的值&#xff0c;使用它的好處就是可以統一對這些字段進行處理&#xff0c;降低了冗余代碼的數量。本…

【前端】從零開始的搭建結構(技術棧:Node.js + Express + MongoDB + React)book-management

項目路徑總結 后端結構 server/ ├── controllers/ # 業務邏輯 │ ├── authController.js │ ├── bookController.js │ ├── genreController.js │ └── userController.js ├── middleware/ # 中間件 │ ├── authMiddleware…

【RAG】向量?知識庫的底層原理:向量數據庫の技術鑒賞 | HNSW(導航小世界)、LSH、K-means

一、向量化表示的核心概念 1.1 特征空間與向量表示 多維特征表示&#xff1a;通過多個特征維度&#xff08;如體型、毛發長度、鼻子長短等&#xff09;描述對象&#xff0c;每個對象對應高維空間中的一個坐標點&#xff0c;來表示狗這個對象&#xff0c;這樣可以區分出不同種…

如何用CSS實現HTML元素的旋轉效果

原文&#xff1a;如何用CSS實現HTML元素的旋轉效果 | w3cschool筆記 &#xff08;本文為科普文章&#xff0c;請勿標記為付費&#xff09; 在網頁制作中&#xff0c;為 HTML 元素設置旋轉效果可使其更靈動&#xff0c;提升用戶體驗。本文將深入淺出地介紹如何利用 CSS 實現 H…

Spark集群搭建之Yarn模式

配置集群 1.上傳并解壓spark-3.1.2-bin-hadoop3.2.tgz&#xff0c;重命名解壓之后的目錄為spark-yarn。 2. 修改一下spark的環境變量&#xff0c;/etc/profile.d/my_env.sh 。 # spark 環境變量 export SPARK_HOME/opt/module/spark-yarn export PATH$PATH:$SPARK_HOME/bin:$SP…

xLua筆記

Generate Code干了什么 肉眼可見的&#xff0c;在Asset文件夾生成了XLua/Gen文件夾&#xff0c;里面有一些腳本。然后對加了[CSharpCallLua]的變量尋找引用&#xff0c;發現它被XLua/Gen/DelegatesGensBridge引用了。也可以在這里查哪些類型加了[CSharpCallLua]。 public over…

【tcp連接windows redis】

tcp連接windows redis 修改redis.conf 修改redis.conf bind * -::*表示禁用保護模式&#xff0c;允許外部網絡連接 protected-mode no

【序列貪心】擺動序列 / 最長遞增子序列 / 遞增的三元子序列 / 最長連續遞增序列

??個人主頁&#xff1a;小羊 ??所屬專欄&#xff1a;貪心算法 很榮幸您能閱讀我的文章&#xff0c;誠請評論指點&#xff0c;歡迎歡迎 ~ 目錄 擺動序列最長遞增子序列遞增的三元子序列最長連續遞增序列 擺動序列 擺動序列 貪心策略&#xff1a;統計出所有的極大值和極小…

STM32F103C8T6使用MLX90614模塊

首先說明&#xff1a; 1.SMBus和I2C的區別 我曾嘗試用江科大的I2C底層去直接讀取該模塊&#xff0c;但是無法成功&#xff0c;之后AI生成的的代碼也無法成功。 思來想去最大的可能就是SMBus這個協議的問題&#xff0c;根據百度得到的結果如下&#xff1a; SMBus和I2C的區別 鏈…

tp5 php獲取農歷年月日干支甲午

# 切換為國內鏡像源 composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/# 再次嘗試安裝 composer require overtrue/chinese-calendar核心寫法一個農歷轉公歷&#xff0c;一個公歷轉農歷 農歷閏月可能被錯誤標記&#xff08;例如 閏四月 應表示…