c 多線程map_Rust:一個不再有 C/C++ 的,實現安全實時軟件的未來

ee784fbc77a591f401a1b7fd3cf451e6.gif

作者丨lochsh

譯者丨馬可薇

策劃丨王文婧

Rust 作為新興編程語言深受 Haskell 和 OCaml 等函數式編程語言的影響,使得它在語法上與 C++ 類似,但在語義上則完全不同。Rust 是靜態類型語言,同時具有完整類型推斷,而不是 C++ 的部分類型推斷,它在速度上可與 C++ 媲美的同時,也保證了內存安全。 索引的故事

在詳細介紹 Rust 之前,我們先舉一個例子。想象你是一個為新房子搭建煤氣管道的工人,你的老板想要你去地下室把煤氣管連到街上的主煤氣管道里,然而你下樓時卻發現有個小問題,這個房子并沒有地下室。所以,現在你要做什么呢?什么都不做,還是異想天開地妄圖通過把煤氣主管道連到隔壁辦公室的空調進氣口來解決問題?不管怎么說,當你向老板匯報任務完成時,你或許會在煤氣爆炸的土灰中以刑事疏忽罪起訴。

這就是在某些編程語言中會發生的事。在 C 里是數組,C++ 里可能是向量,當程序試圖尋找第 -1 個元素時,什么都有可能發生:或許是每次搜索的結果都不同,讓你意識不到這里存在問題。這種被稱作是未定義的行為,它發生的可能性并不能完全被杜絕,因為底層的硬件操作從本質上來說并不安全,這些操作在其他的編程語言里可能會被編譯器警告,但是 C/C++ 并不會。

在無法保證內存安全的情況下,未定義行為極有可能發生。漏洞 HeartBleed,一個著名的 SSL 安全漏洞,就是因為缺少內存安全防護;Stagefright,同樣出名的安卓漏洞,是因為 C++ 里整數溢出造成的未定義行為。

內存安全不止用來提防漏洞,它對應用程序的正確運行和可靠性同樣至關重要。可靠性的重要性在于它可以保證程序不會突然崩潰。至于準確性,作者有一個曾經在火箭飛行模擬軟件公司工作的朋友,他們發現傳遞相同的初始化數據,但是使用不同的文件名會導致不同的結果,這是因為有些未初始化的內存被讀取,因此模擬器就不同文件名的原因而使用了垃圾數值做基礎,可以說他們的這個項目毫無用處。

為什么不用 Python 或 Java 這些可以保障內存安全的語言呢?

Python 和 Java 使用自動垃圾回收來避免內存錯誤,例如:

  • 釋放重引用(Use-After-Free):申請已經被釋放的內存。

  • 多次釋放(double free):對同一片內存區域釋放兩次,導致未定義行為。

  • 內存泄漏:內存沒有被回收,導致系統可用的內存減少。

自動垃圾收集會作為 JVM 或者 Python 解釋器的一部分運行,在程序運行時不斷地尋找不再使用的模塊,釋放他們相對應的內存或者資源。但是這么做的代價很大,垃圾回收不僅速度緩慢還會占用大量內存,而你也永遠不會知道下一秒你的程序會不會暫停運行來回收垃圾。

Python 和 Java 的內存安全犧牲了運行速度。C/C++ 的運行速度則是犧牲了內存的安全性。

這種讓人無法掌控的垃圾回收讓 Python 與 Java 無法應用在實時軟件中,因為你必須要保證你的程序可以在一定時間內完成運行。這并不是比拼運行速度,而是保障你的軟件在每次運行的時候都可以足夠迅速。

當然,C/C++ 如此受歡迎還有其他方面的因素:他們已經存在了足夠長的時間來讓人們習慣他們了。但是他們同樣因為運行速度與運行結果的保障而受到追捧。然而不幸的是,這樣的速度是在犧牲內存安全的前提下。更糟糕的是,許多實時軟件在保障速度的基礎上同樣需要注重安全性,例如車輛或者醫用機器人中的控制軟件。而這些軟件用的仍然是這些并不安全的語言。

在很長的一段時間里,二者處于魚與熊掌不可兼得的狀態,要么選擇運行速度和不可預知性,要么選擇內存安全和可預知性。Rust 則完全顛覆了這一點,這也是它為什么令人激動的原因。

Rust 的設計目標
  • 無需擔心數據的并發運算:只要程序中的不同部分可能在不同的時間或者亂序運行,并發就有可能發生。眾所周知,數據并發在多線程程序中是一個常見的危險因素,這一點我們稍后再詳細描述。

  • 零開銷抽象:指編程語言提供的便利與表現力并不會帶來額外的負擔,也不會降低程序的運行速度。

  • 不需要垃圾回收的內存安全:內存安全和垃圾回收的定義我們已經了解了,接下來我們將詳細闡述 Rsut 是如何平衡速度與安全的關系的。

無需垃圾回收就能實現內存安全

Rust 的內存安全保障說簡單也很簡單,說復雜也是復雜。簡單是因為這里只包含了幾個非常容易理解的規則。

在 Rust 中,每一個對象有且只有一個所有者(owner),確保任何資源只能有一個綁定。為了避免被限制,在嚴格的規則下我們可以使用引用。引用在 Rsut 中經常被稱作“借用(borrowing)”。

借用規則如下:

  • 任何借用的作用域都能不大于其所有者的。

  • 不能在擁有可變引用的同時擁有不可變引用,但是多個可變引用是可以的。

第一個規則避免了釋放重引用的發生,第二個規則排除了數據互斥的可能性。數據互斥會讓內存處于未知狀態,而它可由這三個行為造成:

  • 兩個或更多指針同時訪問同一數據。

  • 至少有一個指針被用來寫入數據。

  • 沒有同步數據訪問的機制。

當作者還是嵌入式工程師的時候,堆(heap)還沒有出現,于是便在硬件上設置了一個空指針解引用的陷阱,這樣一來,很多常見的內存問題就顯得不是那么重要了。數據互斥是作者當時最怕的一種 bug;它難以追蹤,當你修改了一部分看起來并不重要的代碼,或是外部條件發生了微小的改變時,互斥的勝利者也就易位了。Therac-25 事件,就是因為數據互斥使得癌癥病人在治療過程中受到了過量的輻射,因此造成患者死亡或者重傷。

Rust 革新的關鍵也是它聰明的地方,它可以在編譯時強制執行內存安全保障。這些規則對任何接觸過數據互斥的人來說都應當不是什么新鮮事。

不安全的 Rust

如作者之前所說,未定義行為發生的可能性是不能完全被清除的,這是由于底層計算機硬件固有的不安全性導致的。Rust 允許在一個存放不安全代碼的模塊進行不安全操作。C# 和 Ada 應該也有類似禁用安全檢查的方案。在進行嵌入式編程操作或者在底層系統編程的時候,就會需要這樣的一個塊。隔離代碼的潛在不安全部分非常有用,這樣一來,與內存相關的錯誤就必定位于這個模塊內,而不是整個程序的任意部分。

不安全模塊并不會關閉借用檢查,用戶可以在不安全塊中進行解引用裸引針,訪問或修改可變靜態變量,所有權系統的優點仍然存在。

重溫所有權

說起所有權,就不得不提起 C++ 的所有權機制。

C++ 中的所有權在 C++11 發布之后得到了極大的提升,但是它也為向后兼容性問題付出了不小的代價。對于作者來說,C++ 的所有權非常多余,以前簡單的值分類被吊打。不管怎么說,對 C++ 這樣廣泛使用的語言進行大規模優化是一項偉大的成就,但是 Rust 卻是將所有權從一開始就當作核心理念進行設計的語言。

C++ 的類型系統不會對對象模型的生命周期進行建模,因此在運行時是無法檢查釋放后重引用的問題。C++ 的智能指針只是加在舊系統上的一個庫,而這個庫會以 Rust 中不被允許的方式濫用和誤用。

下面是作者在工作中編寫的一些經過簡化后的代碼,代碼中存在誤用的問題。

#include #include #include std::vector createChecksFromStrings(? ? ? ? std::unique_ptr data,? ? ? ? std::vector<std::string> dataCheckStrs) {? ? auto createCheck =  &  {? ? ? ? return DataValueCheck(checkStr, std::move(data));? ? };? ? std::vector checks;? ? std::transform(? ? ? ? ? ? dataCheckStrs.begin(),? ? ? ? ? ? dataCheckStrs.end(),? ? ? ? ? ? std::back_inserter(checks),? ? ? ? ? ? createCheck);? ? return checks;}

這段代碼的作用是,通過字符串 dataCheckStrs 定義對某些數據的檢查,例如一個特定范圍內的值,然后再通過解析這個字符串創建一個用于檢查對象的向量。

首先創建一個引用捕捉的 lambda 表達式,由 & 標識,這個智能指針(unique_ptr)指向的對象在這個 lambda 內被移動,因此是非法的。

然后用被移動的數據構建的檢查填充向量,但問題是它只能完成第一步。unique_ptr 和被指向對象表示一種獨自占有的關系,不能被拷貝。所以在 std::transform 的第一個循環之后,unique_ptr 很有可能被清空,官方聲明是它會處于一種有效但是未知的狀態,但是以作者對 Clang 的經驗來看它通常會被清空。

后續使用這個空指針時會導致未定義行為,作者運行之后得到了一個空指針錯誤,在大多數托管系統的空指針解引用都會報這種錯誤,因為零內存頁面通常會被保留。但當然這種情況并不會百分百發生,這種 bug 在理論上可能會被暫時擱置一段時間,然后等著你的就是程序的突然崩潰。

這里使用 lambda 的方式很大程度上導致了這種危險的發生。編譯器在調用時只能看到以一個函數指針,它并不能像標準函數那樣檢查 lambda。

結合上下文來理解這個 bug 的話,最初使用 shared_ptr 來存儲數據,這一部分沒有問題。然而我們卻錯誤地將數據存儲在了 unique_ptr 里,當我們試圖進行更改時就會有問題,它并沒有引起注意是因為編譯器并沒有報錯。

這是 C++ 內存安全問題并沒有引起重視的真實例子,作者和審核代碼的人直到一次測試前都沒有注意到這點。不管你有多少年的編程經驗,這類 bug 根本躲不開!哪怕是編譯器都不能拯救你。這時就需要更好的工具了,不僅僅是為了我們的理智著想,也是為了公眾安全,這關乎職業道德。

接下來讓我們看一看同樣問題在 Rust 中的體現。

在 Rust 中,這種糟糕的 move() 是不會被允許的。

pub fn create_checks_from_strings(? ? ? ? data: Box,? ? ? ? data_check_strs: Vec<String>)? ? -> Vec{? ? let create_check = |check_str: &String| DataValueCheck::new(check_str, data);? ? data_check_strs.iter().map(create_check).collect()}

這是我們第一次看到 Rust 的代碼。需要注意的是,默認情況下變量都是不可變的,但可以在變量前加 mut 關鍵詞使其可變,mut 類似于 C/C++ 中的 const 的反義詞。

Box 類型則表示我們已經在堆上分配了內存,在這里使用是因為 unique_ptr 同樣可以分配到堆。因為 Rust 中每個對象一次有且僅有一個所有者的規則,我們并不需要任何 unique_ptr 類似的東西。接著創建一個閉包,用更高階的函數 map 轉換字符串,類似 C++ 的方式,但并不顯得冗長。但當編譯的時候還是會報錯,下面是錯誤信息:

error[E0525]: expected a closure that implements the `FnMut` trait, but this closure only implements `FnOnce`? ?--> bad_move.rs:1:8? ? |? 6 |? ? ?let create_check = |check_str: &String| DataValueCheck::new(check_str, data);? ? |? ? ? ? ? ? ? ? ? ? ? ? ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^----^? ? |? ? ? ? ? ? ? ? ? ? ? ? |? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?|? ? |? ? ? ? ? ? ? ? ? ? ? ? |? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?closure is `FnOnce` because it moves? ? |? ? ? ? ? ? ? ? ? ? ? ? |? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?the variable `data` out of its environment? ? |? ? ? ? ? ? ? ? ? ? ? ? this closure implements `FnOnce`, not `FnMut`? 7 |? ? ?data_check_strs.iter().map(create_check).collect()? ? |? ? ? ? ? ? ? ? ? ? ? ? ? ? --- the requirement to implement `FnMut` derives from here  error: aborting due to previous error??For more information about this error, try `rustc --explain E0525`.

Rust 社區有一點很棒,它提供給人們的學習資源非常多,也會提供可讀性的錯誤信息,用戶甚至可以向編譯器詢問關于錯誤的更詳細信息,而編譯器則會回復一個帶有解釋的最小示例。

當創建閉包時,由于有且僅有一個所有者的規則,數據是在其內被移動的。接下來編譯器推斷閉包只能運行一次:沒有所有權的原因,多次的運行是非法的。之后 map 函數就會需求一個可以重復調用并且處于可變狀態的可調用函數,這就是為什么編譯器會失敗的原因。

這一段代碼顯示了 Rust 中類型系統與 C++ 相比有多么強大,同時也體現了在當編譯器跟蹤對象生命周期時的語言中編程是多么不同。

在示例中的錯誤信息里提到了特質(trait)。例如:”缺少實現 FnMut 特質的閉包“。特質是一種告訴 Rust 編譯器某個特定類型擁有功能的語言特性,特質也是 Rust 多態機制的體現。

多態性

C++ 支持多種形式的多態,作者認為這有助于語言的豐富性。靜態多態中有模板、函數和以及操作符重載;動態多態有子類。但這些表達形式也有非常明顯的缺點:子類與父類之間的緊密耦合,導致子類過于依賴父類,缺乏獨立性;模板則因為其缺乏參數化的特性而導致調試困難。

Rust 中的 trait 則定義了一種指定靜態動態接口共享的行為。Trait 類似于其他語言中接口(interface)的功能,但 Rust 中只支持實現(implements)而沒有繼承(extends)關系,鼓勵基于組合的設計而不是實現繼承,降低耦合度。

下面來看一個簡單又有趣的例子:

trait Rateable {? ? /// Rate fluff out of 10? ? /// Ratings above 10 for exceptionally soft bois? ? fn fluff_rating(&self) -> f32;}struct Alpaca {? ? days_since_shearing: f32,? ? age: f32}impl Rateable for Alpaca {? ? fn fluff_rating(&self) -> f32 {? ? ? ? 10.0 * 365.0 / self.days_since_shearing? ? }}

首先定義一個名為 Rateable 的 trait,然后需要調用函數 fluff_rating 并返回一個浮點數來實現 Rateable。接著就是在 Alpaca 結構體上對 Rateable trait 的實現。下面是使用同樣的方法定義 Cat 類型。

enum Coat {? ? Hairless,? ? Short,? ? Medium,? ? Long}struct Cat {? ? coat: Coat,? ? age: f32}impl Rateable for Cat {? ? fn fluff_rating(&self) -> f32 {? ? ? ? match self.coat {? ? ? ? ? ? Coat::Hairless => 0.0,? ? ? ? ? ? Coat::Short => 5.0,? ? ? ? ? ? Coat::Medium => 7.5,? ? ? ? ? ? Coat::Long => 10.0? ? ? ? }? ? }}

在這段例子中作者使用了 Rust 的另一特性,模式匹配。它與 C 中的 switch 語句用法類似,但在語義上卻有很大的區別。switch 塊中的 case 只能用來跳轉,模式匹配中則要求覆蓋全部可能性才能編譯成功,但可選的匹配范圍和結構則賦予了其靈活性。

下面是這兩種類型的實現結合得出的通用函數:

fn pet(boi: T) -> &str {? ? match boi.fluff_rating() {? ? ? ? 0.0...3.5 => "naked alien boi...but precious nonetheless",? ? ? ? 3.5...6.5 => "increased floof...increased joy",? ? ? ? 6.5...8.5 => "approaching maximum fluff",? ? ? ? _ => "sublime. the softest boi!"}

尖括號中的是類型參數,這一點和 C++ 中相同,但與 C++ 模板的不同之處在于我們可以使函數參數化。“此函數只適用于 Rateable 類型”的說法在 Rust 中是可以的,但在 C++ 中卻毫無意義,這帶來的后果不僅限于可讀性。類型參數上的 trait bound 意味著 Rust 的編譯器可以只對函數進行一次類型檢查,避免了單獨檢查每個具體的實現,從而縮短編譯時間并簡化了編譯錯誤信息。

Trait 也可以動態使用,雖然有的時候是必須的,但是并不推薦,因為會增加運行開銷,所以作者在本文中并沒有詳細提及。Trait 中另一大部分就是它的互通性,例如標準庫中的 Display 和 Add trait。實現 add trait 意味著可以重載運算符 +,實現 display trait 則意味著可以格式化輸出顯示。

Rust 的工具

C/C++ 中并沒有用于管理依賴的標準,倒是有不少工具可以提供幫助,但是它們的口碑都不是很好。基礎的 Makefiles 用于構建系統非常靈活,但在維護上就是一團垃圾。CMake 減少了維護的負擔,但是它的靈活性較弱,又很讓人煩惱。

Rust 在這方面就很優秀,Cargo 是唯一 Rust 社區中唯一的可以用來管理包和依賴,同時還可以用來搭建和運行項目。它的地位與 Python 中的 Pipenv 和 Poetry 類似。官方安裝包會自帶 Cargo,它好用到讓人遺憾為什么 C/C++ 中沒有類似的工具。

0eafca86e2926676db262d579d1889af.png

我們難道都要轉向 Rust 嗎?

這個問題沒有標準答案,完全取決于用戶的應用程序場景,這一點在任何編程語言中都是共通的。Rust 在不同方面都有成功的案例:包括微軟的 Azure IoT 項目,Mozilla 也支持 Rust 并將用于部分火狐瀏覽器中,同樣很多人也在使用 Rust。Rust 已經日漸成熟并可以用于生產,但對于某些應用程序來說,它可能還不夠成熟或缺乏支持庫。

1、嵌入式:在嵌入式的環境中,Rust 的使用體驗完全由用戶定義用它做什么。Cortex-M 已經資源成熟并可以用于生產了,RISC-V 也有了一個還在發展尚未常熟的工具鏈。.

x86 和 arm8 架構也發展得不錯,其中就有 Raspberry Pi。像是 PIC 和 AVR 這樣的老式架構還有些欠缺,但作者認為,對于大多數的新項目來說應該沒什么大問題。

交叉編譯支持也適用于所有的 LLVM(Low-Level Virtual Machine)的目標,因為 Rust 使用 LLVM 作為其編譯器后端。

Rust 在嵌入式中缺少的另一個部分是生產級的 RTOS,在 HAL 的發展也很匱乏。對許多項目來說,這沒什么大不了了,但對另一些項目的阻礙依舊存在。在未來幾年內,阻礙可能還會繼續增加。

2、異步:語言的異步支持還尚在開發階段,async/await 的語法都還未被確定。

3、互通性:至于與其他語言的互操作性,Rust 有一個 C 的外部函數接口(FFI),無論是 C++ 到 Rust 函數的回調還是將 Rust 對象作為回調,都需要經過這一步。在很多語言中這都是非常普遍的,在這里提到則是因為如果將 Rust 合并到現有的 C++ 項目中會有些麻煩,因為用戶需要在 Rust 和 C++ 中添加一個 C 語言層,這毫無疑問會帶來很多問題。

寫在最后

如果要在工作中從頭開始一個項目,那么作者絕對會選擇 Rust 編程語言。希望 Rust 可以成為一個更可靠,更安全,也更令人享受的未來編程語言。

原文鏈接:

https://mcla.ug/blog/rust-a-future-for-real-time-and-safety-critical-software.html

0c640c249a45e631abde285a061e1aca.gif

點個在看少個 bug ?

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

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

相關文章

SQLServer中交叉聯接的用法介紹

目錄 1、交叉聯接(cross join)的概念 2、交叉聯接的語法格式 3、交叉查詢的使用場景 3.1 交叉聯接可以查詢全部數據 3.2 交叉聯接優化查詢性能 4、總結 今天給大家介紹SQLServer中交叉聯接的用法&#xff0c;希望對大家能有所幫助&#xff01; 1、交叉聯接(cross join)的概念 交…

計算機有關的文獻檢索題目,文獻檢索第二次計算機檢索實習題目(2016.4.10).doc

文檔介紹&#xff1a;第二次計算機檢索實****題目本次實****目的是使學生對以下三個外文數據庫:PENDEX數據庫、荷蘭SDOS數據庫、德國Springer數據庫的使用方法有所了解。通過題目,使學生分別掌握每個數據庫所提供的各種獲取文獻的方法,包括瀏覽和檢索的方法,并通過各種算符的組…

spring中的IOC和AOP

IOC IOC&#xff08;Inversion Of Controll&#xff0c;控制反轉&#xff09;是一種設計思想&#xff0c;將原本在程序中手動創建對象的控制權&#xff0c;交由給Spring框架來管理。IOC容器是Spring用來實現IOC的載體&#xff0c;IOC容器實際上就是一個Map(key, value)&#x…

HDU1598【最小生成樹拓展】

參考自 http://www.cnblogs.com/nanke/archive/2012/02/13/2350008.html PS: 沒想到最小生成樹的kruskal算法從小到大枚舉邊&#xff0c;然后MAX-MIN就保證了最小。 不過居然沒想到這個方案&#xff0c;就是二分枚舉差值&#xff0c;然后跑最短路&#xff1b; 轉載于:https://w…

python圖表可視化工具_比Excel制圖更強大,Python可視化工具Altair入門教程

原作者 Parul Pandey曉查 編譯整理量子位 出品 | 公眾號 QbitAI數據轉化成更直觀的圖片&#xff0c;對于理解數據背后的真相很有幫助。如果你有這方面的需求&#xff0c;而且還在使用Python&#xff0c;那么強烈推薦你試一試Altair。Altair是一個專為Python編寫的可視化軟件包&…

后端技術:SpringBoot配置熱加載工具(devtools)筆記

今天給大家介紹IDEA開發工具如何配置devtools熱加載工具。1、devtools原理介紹spring-boot-devtools是spring為開發者提供的熱加載工具包。主要原理是使用了兩個ClassLoader類加載器&#xff0c;一個Classloader加載那些不會改變的類&#xff08;第三方Jar包&#xff09;&#…

關于ios手機上傳圖片旋轉問題的解決

先占個坑,一會完善!! 轉載于:https://www.cnblogs.com/zhuyu/articles/5943400.html

服務器不能創建對象教務系統,[轉]解決強智教務系統非IE下無法創建對象錯誤...

最近要弄教務網的模擬登陸&#xff0c;但苦于教務網只兼容IE8以下的瀏覽器&#xff0c;不能用chrome強大的F12抓包就很煩&#xff0c;然后發現Fly俊大佬弄了一個相當強的插件啊。但是怕Fly俊大佬的博客失效&#xff0c;所以私自留了個檔。侵刪啊大佬~以下內容均為轉載學校教務系…

Java8對List<Integer>的求和

一般都是對對象對某個字段求和&#xff0c;對List<Object>中Object中的某個字段進行求和&#xff0c;就像這樣&#xff1a; 如果想要用流對List<Integer>進行求和&#xff0c;如下 long sum receiveTotalPriceList.stream().reduce(Integer::sum).orElse(0);

安裝python無法啟動_終端里為什么無法運行python?

1、首先確認自己電腦是否安裝了python&#xff0c;如果不確定可以在終端直接輸入python&#xff0c;出現以下界面說明已經正確安裝&#xff1a;2、如果已經安裝了python&#xff0c;但是在終端還是顯示未安裝&#xff0c;很有可能是沒有配置環境變量。環境變量的配置步驟如下&a…

電腦軟件:主流的壓縮軟件對比,看完你就會選擇了

目錄 1、WinRAR 2、360壓縮 3、7-ZIP 4、Bandizip 5、好壓/快壓 今天小編給大家介紹一下主流的壓縮軟件&#xff0c;看完你就會選擇了&#xff01; 目前主流的壓縮軟件主要有WinRAR、360壓縮、7-ZIP、Bandzip、好壓。現在給大家逐個介紹一下&#xff0c;每款壓縮軟件的特。 1、…

怎么測試ajax get請求,為什么我的AJAX在運行測試時請求dev中的PUT請求,但請求了GET請求?...

在我的Rails應用程序中&#xff0c;我制作了一個AJAX PUT請求(使用Coffeescript)&#xff0c;該請求在我的開發環境中正常工作。當我檢查按鈕&#xff0c;點擊后&#xff0c;會觸發使用Firefox的AJAX請求時&#xff0c;它看起來是這樣的&#xff1a;為什么我的AJAX在運行測試時…

控制DIV內容滾動的方法,實現不用拖滾動條就可以看到最新消息

三種控制DIV內容滾動的方法&#xff1a; 本人qq群也有許多的技術文檔&#xff0c;希望可以為你提供一些幫助(非技術的勿加)。 QQ群&#xff1a; 281442983 (點擊鏈接加入群&#xff1a;http://jq.qq.com/?_wv1027&k29LoD19) 方法一&#xff1a;使用錨標記要滾動到的位置…

python excel模板 插件_python腳本——自動推送txt,EXCEL等文件模板

#!/usr/bin/python# -*- coding: utf-8 -*- #此處支持中文字符一定要放在前兩行&#xff0c;置后的話不生效&#xff0c;腳本報錯#!/usr/bin/env pythonimport smtplibfrom email.mime.text import MIMETextimport stringimport timeimport datetimeHOST "mail.xxxxxx.co…

電腦軟件:推薦10款實用的辦公效率神器

目錄 1.Everything 搜索神器 2.IDM 下載器 3.Dism 電腦維護工具 4.Captura 錄屏神器 5.Hourglass 倒計時軟件 6.Ditto剪切板增強工具 7.Snipaste 截圖神器 8.ScreenToGif GIF動態圖片錄制軟件 9.QuickLook文檔查看工具 10.processlasso 任務管理 1.Everything 搜索神器 Everyth…

財務系統專用服務器中標公告,東南大學財務處服務器存儲-招標辦公室.doc

東南大學財務處服務器存儲-招標辦公室東南大學財務處服務器、存儲、防雷系統采購招標公告項目名稱&#xff1a;標段一&#xff1a;一卡通數據庫生產服務器、一卡通數據庫容災服務器標段二&#xff1a;一卡通系統刀片服務器標段三&#xff1a;一卡通存儲設備標段四&#xff1a;一…

Finereport集群配置

增加配置文件cluster.xml 將配置包resource文件夾下的cluster.xml打開&#xff0c;如果沒有cluster.xml&#xff0c;則新建一個&#xff0c;基本內容如下&#xff1a; <?xml version"1.0" encoding"UTF-8" ?> <ClusterConfig useCluster"t…

deepin桌面為什么那么卡_deepin因NVIDIA顯卡造成開機啟動問題:卡在開機logo界面+進入桌面鼠標一直轉圈...

寫在前面&#xff1a;前兩天為了做人工智能的作業&#xff0c;給自己和室友裝了一個deepin15.7的系統(我和室友電腦均是NVIDIA顯卡)&#xff0c;想著自己裝好之后不會出現什么問題&#xff0c;結果錯誤還是如期而至&#xff0c;無奈&#xff0c;只能自己親自想辦法&#xff0c;…