日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

muduo学习笔记 - 第1章 C++多线程系统编程

發布時間:2024/4/18 c/c++ 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 muduo学习笔记 - 第1章 C++多线程系统编程 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

第1章 C++多線程系統編程

1.1 智能指針

C++中動態內存管理是用new和delete完成。

動態內存管理經常出現兩種問題:

  • 忘記釋放內存造成內存泄露
  • 還有指針引用的內存的情況下釋放內存,造成引用非法內存指針

智能指針負責自動釋放所指向的對象

1.2 構造函數

  • 如何判斷構造函數的執行結果?
    • 構造沒有返回值不能通過返回值判斷
    • 狀態量,拋出異常?
    • 條件判斷,例如構造函數的功能,如果涉及內存申請,判斷地址是否合法
    • 二段式構造
  • 構造函數中執行return, return之后的語句不再執行,生成一個半成品的對象
  • 構造函數執行結束不意味著對象構造成功,內存申請不一定成功

構造函數只提供初始化變量,不保證初始化一定成功

1.3 二段式構造

對象的構造不能在構造期間泄露this指針,否則會返回一個半成品的對象

二段式構造將構造分為兩個階段,第一個階段為資源無關的初始化,第二階段是與系統資源相關的初始化(申請內存,訪問文件)

#include <iostream> using namespace std;class Test { public:char* p;int val;Test() { // 第一階段構造val = 10;}bool construct() { // 第二階段構造p = new char('a');if (p == nullptr)return false;elsereturn true;}static Test* instance() {Test* ret = new Test(); // 執行第一階段構造if (ret == nullptr || ret->construct() == false) { // 執行第二階段構造delete ret;ret = nullptr;}return ret;}~Test() {delete p;p = nullptr;} };void run() {Test* t = Test::instance();if (t == nullptr) {cout << "False\n";}else {cout << t->val << " " << *(t->p) << endl;} }

使用二段式構造就不能使用默認的構造函數直接生成對象,否則會忽略第二階段的construct函數。通過使用instance函數保證構造函數的完整運行。

1.4 析構函數的線程安全

析構函數在多線程中使用mutex不一定能保證線程的安全性

成員函數用來保護臨界區的互斥器mutex是必要的,但是析構函數破壞了這一假設,它直接包mutex成員變量銷毀了。

Foo::~Foo() {MutexLockGuard lock(mutex_);// free internal state (1) }void Foo::update() {MutexLockGuard lock(mutex_); (2)// make use of internal state }extern Foo* x; // 對每個線程可見// thread A delete x; x = NULL;// thread B if (x) {x->update(); }

雖然線程A銷毀對象之后將其設置NULL, 線程B在執行的時候判斷指針是否為NULL, 但是當A持有互斥鎖處在(1), B阻塞處在(2)。當析構函數把mutex銷毀之后,線程B可能永久阻塞、進入臨界區、coredump…

1.5 多個對象的mutex

低地址加鎖

void swap(Counter& a, Counter& b) {MutexLockGuard aLock(a.mutex_); // potential dead lockMultexLokGuard bLock(b.mutex_);int64_t value = a.value_;a.value_ = b.value_;b.value_ = value; }

如果有兩個線程, A執行swap(a, b), 執行swap(b, a), 就可能發生死鎖.

如果一個函數要鎖住多個相同類型的多個對象, 為了保證始終按照相同順序加鎖, 可以比較mutex對象的地址, 始終先對地址小的加鎖

1.6 智能指針

shared_ptr

通過引用計數可以不必擔心多個指針指向同一個對象,何時銷毀對象的問題,但同同時也存在引用成環的問題。

shared_ptr不是100%線程安全的,引用計數本身是安全且無鎖的,對象的讀寫不是,shared_ptr有兩個數據成員,讀寫操作不能原子化

class A { public:int i;A(int n) : i(n) {}~A() {cout << i << " is destructed" << endl;} };void run() {shared_ptr<A> p1(new A(1)); // A(1)由p1托管shared_ptr<A> p2(p1); // A(1)同時由p2托管shared_ptr<A> p3;p3 = p2; // A(1)同時由p3托管cout << p1->i << " " << p2->i << " " << p3->i << endl; // 輸出 1 1 1A* p = p1.get(); // get返回托管的指針, p指向A(1)cout << p->i << endl; // 輸出1p1.reset(new A(2)); // p1和p2托管新的指針p2.reset(new A(3));p3.reset(new A(4));// 無人托管A(1), A(1)自動析構cout << "over" << endl;// 函數結束后,A(2),A(3),A(4)也析構 }

weak_ptr

配合shared_ptr解決引用成環的問題

先來看看引用成環的例子

class B; class A { public:A() {cout << "A的構造函數\n";}~A() {cout << "A的析構函數\n";}void set_ptr(shared_ptr<B>& ptr) {m_ptr_b = ptr;}void show() {cout << "this is class A" << endl;} private:shared_ptr<B> m_ptr_b; };class B { public:B() {cout << "B的構造函數\n";}~B() {cout << "B的析構函數\n";}void set_ptr(shared_ptr<A>& ptr) {m_ptr_a = ptr;}void show() {cout << "this is class B" << endl;} private:shared_ptr<A> m_ptr_a; };void run() {shared_ptr<A> ptr_a(new A());shared_ptr<B> ptr_b(new B());cout << "ptr_a use count: " << ptr_a.use_count() << endl;cout << "ptr_b use count: " << ptr_b.use_count() << endl;ptr_a->set_ptr(ptr_b);ptr_b->set_ptr(ptr_a);cout << "ptr_a use count: " << ptr_a.use_count() << endl;cout << "ptr_b use count: " << ptr_b.use_count() << endl; } /*A的構造函數B的構造函數ptr_a use count: 1ptr_b use count: 1ptr_a use count: 2ptr_b use count: 2 */

當run函數運行結束之后,ptr_a和ptr_b被銷毀,但是A和B的析構函數沒有執行。這是因為A和B內部的ptr的引用不為0導致指向的內部對象無法析構,造成內存泄漏。

解決的方法是將A和B中的一個成員變量改為weak_ptr,weakptr不會增加引用計數,引用不會成環,析構就可以順利進行

public:B() {cout << "B的構造函數\n";}~B() {cout << "B的析構函數\n";}void set_ptr(shared_ptr<A>& ptr) {m_ptr_a = ptr;} private:weak_ptr<A> m_ptr_a; };/*A的構造函數B的構造函數ptr_a use count: 1ptr_b use count: 1ptr_a use count: 1ptr_b use count: 2A的析構函數B的析構函數 */

shared_ptr是強引用,控制對象的生命期

weak_ptr是弱引用,不控制對象的生命期

void run() {shared_ptr<A> ptr_a(new A());weak_ptr<A> wk_ptr_a = ptr_a;cout << "ptr_a use count: " << ptr_a.use_count() << endl; // 輸出1if (!wk_ptr_a.expired()) {wk_ptr_a.lock()->show();}wk_ptr_a.reset();if (wk_ptr_a.expired()) {cout << "wk_ptr_a is invalid" << endl;} }
  • weak_ptr不會增加引用計數,不擁有對象,通過lock方法臨時擁有對象

  • 通過expired判斷對象的生死

1.7 C++的內存問題

  • 緩沖區溢出 (buffer overrun)
  • 空懸針/野指針
  • 重復釋放 (double delete)
  • 內存泄漏 (memory leak)
  • 不配對的new[]/delete
  • 內存碎片 (memory fragmentation)
  • 內存泄漏的危害性較小,他只是借東西不還,程序功能在一段時間內還算正常。緩沖區溢出和重復釋放等會造成安全性的嚴重后果

    總結

    以上是生活随笔為你收集整理的muduo学习笔记 - 第1章 C++多线程系统编程的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。