主頁 > 軟體工程 > 當從另一個執行緒呼叫它時,我可以防止在存盤的c lambda中的“this”實體指標失效/破壞嗎?

當從另一個執行緒呼叫它時,我可以防止在存盤的c lambda中的“this”實體指標失效/破壞嗎?

2021-11-10 02:46:11 軟體工程

這個問題是有點延期的這一個,因為它是一個類似的問題,但并沒有完全回答我的問題,就像我想(更不用提在這一問題的問題不涉及執行緒/多執行緒)。

問題

就像標題所暗示的那樣,我遇到了一個關于 lambdas 和std::threads的特定問題,我的背景知識無法幫助我。我本質上是在嘗試將std::function包含成員函式的存盤在一個靜態類中,以便稍后在單獨的std::thread. 問題是這個成員函式需要參考this實體指標因為這個成員函式修改了同一個類的資料成員,但是this通過參考傳遞給 lambda指標在呼叫成員函式時就失效/銷毀了,這導致了似乎是未定義的行為。這個問題實際上在提案檔案P0018R3 中有一些討論,它討論了 lambdas 和并發性。

半最小作業示例

//Function class
struct FuncWrapper {
    //random data members
    std::string name;
    std::string description;
    //I still have this problem even if this member is const
    std::function<void()> f;
};
class FuncHolder {
public:
    //For the FuncWrapper arguement, I've tried pass by reference, const-reference, rvalue, etc. doesn't make a difference in terms of the problem (as I expect)
    //add a FuncWrapper object to the static holder
    static void add(FuncWrapper f) {
        return const_cast<std::vector<FuncWrapper>&>(funcs).push_back(f);
    }
    //Same here with the return
    //get a static FuncWrapper object by index (with bounds checking)
    static const std::function<void()>& get(size_t index) {
        return funcs.at(index);
    }
private:
    //C  17 inline static definition; If I weren't using it I would do it the non-inline way
    const static inline std::vector<FuncWrapper> funcs;
};
//Data member class
struct Base {
    //This is meant to be like a constexpr; its unique for each derived type
    inline virtual const char* const getName() const = 0;
protected:
    //This will be important for a solution I tried
    const size_t storedIdx = 0;
};
class Derived : public Base {
public:
    Derived(){
        FuncHolder::add({"add5", "adds 5", 
            [&](){increment(5);} //This is the problem-causing statement
        });
        FuncHolder::add({"add1", "adds 1", 
            std::bind(&FuncHolder::increment, 1) //This syntax also causes the same problem
        }); 
    }
    inline const char* const getName() const override {
        return "Derived";
    }
    void increment(int amount){
         //Do some stuff...
         privMember  = amount;
         //Do some other stuff..
    }
private:
    int privMember = 0;
};

//Class to hold the static instances of the different derived type
class BaseHolder {
public:
    //make a new object instace in the static vector
    template<class DerivedTy>
    static void init(const DerivedTy& d){
        static_assert(std::is_base_of_v<Base, DerivedTy>); //make sure it's actually derived
        size_t idx = baseVec.size(); //get the index of the object to be added
        const_cast<std::vector<std::unique_ptr<Base>>&>(baseVec).emplace_back(std::make_unique<DerivedTy>(d)); //forward a new unique_ptr object of derived type
        const_cast<size_t&>(baseVec.at(idx)->storedIdx) = idx; //store the object's index in the object itself
    }
    ///This function is used later for one of the solutions I tried; it goes unused for now
    ///So, assume the passed size_t is always correct in this example
    ///There's probably a better way of doing this, but ignore it for the purposes of the example
    //get a casted reference to the correct base pointer
    template<class DerivedTy>
    static DerivedTy& getInstance(size_t derivedIdx){
        return *(static_cast<DerivedTy*>(baseVec.at(derivedIdx).get()));
    }
private:
    //C  17 inline static again
    const static inline std::vector<std::unique_ptr<Base>> baseVec{};
};
int main() {
    BaseHolder::init(Derived()); //add a new object to the static holder
    //Do stuff...
    std::thread runFunc([&](){
        FuncHolder::Get(0)(); //Undefined behavior invoked here; *this pointer used in the function being called is already destroyed
    });
    //Main thread stuff...
    runFunc.join();
    return 0;
}

It may not be a super minimal example, but I wanted to highlight the important details (such as how the function is stored and the class(es) that call them) so that it's clear how the problem originates.
There's also a few possibly unrelated yet important parts of the design to point out.

  1. it's intended for there to be many classes/types deriving the Base class (i.e. Derived1, Derived2, etc.), but there will only be one instance of each of those derived classes; Hence why all members of the BaseHolder class are static. So if this design does need to be reworked, keep this in mind (though honestly maybe this could be implemented in a better way than it is now, but that may be unrelated to the problem).
  2. It may be instinctual to make the BaseHolder class be templated and just pass the classes/types that I want it to hold to its template at compile time (and thus use something like a tuple instead of a vector), but I didn't do that on purpose because I may need to add more Derived types later in runtime.
  3. I can't really change the template type of f (the std::function<>) because I may need to pass different functions with their own return type and arguments (which is why I use a lambda sometimes and std::bind at times when I just want the callable to a be a function with void return type). In order to accomplish this I just make it a std::function<void()>
  4. The overall goal of this design is to statically call and invoke a function (as if it were triggered by an event) that has been constructed before being called and has the ability to modify a given class (specifically the class its constructed in - Derived in this case).

Problem Origin

Looking into this problem, I know that the this pointer captured by reference in a lambda can be invalidated by the time the lambda runs in a different thread. Using my debugger, I seems like the this pointer was destroyed by the time the lambda was being constructed in the constructor of Derived, which went against my previous knowledge, so I can't be 100% sure this is what's going on; The debugger showed that the entire this instance of Derived was filled with junk values or was unreadable

Derived(){
    FuncHolder::add({"add5", "adds 5", //`this` pointer is fine here
       [&](){increment(5);} //`this` pointer is filled with junk and pointing to a different random address
    });
    //...
}

I'm more sure though about the undefined behavior when the lambda/function is invoked/ran due to its seemingly destroyed this instance of Derived pointer. I get different exceptions each time, from different files, and sometimes just get a flat out access read access violation and what not sometimes. The debugger also can't read the memory of the this pointer of the lambda when it comes around to invoking it; All look like signs of a destroyed pointer.
I've also dealt with this type of problem before in lambdas and know what to do when std::threads are not involved, but the threads seem to complicate things (I'll explain that more later).

What I've Tried

Capture by value

The easiest solution would be to just make the lambda capture the this pointer of Derived by value (as mentioned in both the answer to the aforementioned question and proposal document P0018R3) since I'm using C 17. The proposal document even mentions how capturing this by value is necessary for concurrent applications such as threading:

Derived(){
    FuncHolder::add({"add5", "adds 5", 
        [&, *this](){increment(5);} //Capture *this by value (C  17); it's thread-safe now
    });
    //...
}

The problem with this is, like I said, the functions passed into/captured by the lambda need to modify data members of the class; if I capture this by value, the function is just modifying a copy of the Derived instance, instead of the intended one.

Use Static Instance

Okay, if each derived class is only supposed to have one static instance, and there's a static holder of derived classes, why not just use the static instance in the lambda and modify that instance directly?:

Derived(){
    FuncHolder::add({"add5", "adds 5", 
        [=](){BaseHolder::getInstance(storedIdx)::increment(5);} //use static instance in lambda; Again assume the passed index is always correct for this example
    });
    //...
}

This might look good on paper, but the problem is that the getInstance() is being called in the constructor, before the actual instance is created using the constructor. Specifically, the Derived() constructor is called in BaseHolder::init(Derived()) where init tries to create the instance in vector in the first place; But, the vector is accessed in the Derived() constructor, which is called before init is.

Pass Static Instance to Member Function

Another answer in the aforementioned question says to change the function in the lambda to have a arguement that takes an instance of its class. In our example, it would look something like this:

class Derived : public Base {
public:
    Derived(){
        FuncHolder::add({"add5", "adds 5", 
            [&](){increment(BaseHolder::getInstance(storedIdx), 5);} //Pass the static instance to the actual function
        });
        //...
    }
    //rest of the class...
    void increment(Derived& instance, int amount){
         //Do some stuff...
         instance.privMember  = amount;
         //Do some other stuff..
    }
private:
    int privMember = 0;
};

But this as the same problem as the previous attempted solution (using the static instance in the lambda): the static instance isn't created yet because it's calling the constructor accessing the instance to create it.

shared_ptr of this (directly)

A solution mentioned more than once in the aforementioned question was to make and use a shared_ptr (or any smart pointer for that matter) of this to extend its lifetime and what not (though the answers did not go into depth on how to implement it). The quick-and-dirty way to do this is directly:

Derived(){
    FuncHolder::add({"add5", "adds 5", 
        [self=std::shared_ptr<Derived>()](){self->increment(5);} //pass a shared_ptr of *this; syntax can differ
    });
    //...
}

The problem with this is that you get a std::bad_weak_ptr exception, as doing it this way is probably incorrect anyway (or at least I assume).

shared_ptr of this (std::enable_shared_from_this<T>)

The solution in this blog post, and the solution I usually use when threads are not involved, is to make use of std::enable_shared_from_this<T>::shared_from_this to capture a proper shared_ptr of this:

class Derived : public Base, public std::enable_shared_from_this<Derived> {
    Derived(){
        FuncHolder::add({"add5", "adds 5", 
            [self=shared_from_this()](){self->increment(5);} //pass a shared_ptr of *this
        });
        //...
    }
    //rest of class...
}

This looks good on paper, and doesn't really cause any exceptions, but it doesn't seem to change anything; The problem still remains and it seems no different than just capturing this by reference normally.

Conclusion

Can I prevent the destruction/invalidation of the this pointer of the derived class in the lambda by the time it's called in another thread? If not, what's the right way to do what I am trying to achieve? I.e. how could I rework the design so it functions properly while still keeping my design principles preserved?

uj5u.com熱心網友回復:

我認為有很多解決方案可以在這里作業,但這不是一個“簡短”的問題。這是我對兩種方法的看法:

1 - 促進意圖

看起來您需要在物件和 lambda 之間共享一些狀態。覆寫生命周期很復雜,所以按照你的意圖去做,并將公共狀態提升到一個共享指標中:

class Derived // : Base classes here
{
  std::shared_ptr<DerivedImpl> _impl;

public:
  /*
  Public interface delegates to _impl;
  */
};

這提供了與共享狀態進行兩種互動方式的能力:

// 1. Keep the shared state alive from the lambda:
func = [impl = _impl]() { /* your shared pointer is valid */ };

// 2. Only use the shared state if the original holder is alive:
func = [impl = std::weak_ptr(_impl)]() {
  if (auto spt = impl.lock())
  {
    // Use the shared state
  }
};

畢竟這種enable_shared_from_this方法對您不起作用,因為您一開始沒有共享狀態。通過在內部存盤共享狀態,您可以保留原始設計的值語意并促進生命周期管理變得復雜的使用。

2 - 將狀態放在安全的地方

保證某個狀態在某個點“活著”的最安全方法是將其放在更高的范圍內:

  1. 靜態存盤/全域范圍
  2. 比派生類和 lambda 兩個用戶都高(或低,取決于您的記憶心智模型)的堆疊幀。

擁有這樣一個“存盤”將允許您對物件進行“放置新”,在不使用免費存盤(堆)的情況下構建它們。然后將此功能與參考計數配對,以便最后一個參考派生物件的物件將是呼叫解構式的物件。

這個方案并不容易實作,如果這個層次結構有主要的性能要求,你應該只為它而努力。如果您決定沿著這條路走下去,您還可以考慮記憶體池,它提供這種生命周期操作,并預先解決了許多相關的難題。

uj5u.com熱心網友回復:

這是基于您的實際源代碼的更新版本。

#include <future>
#include <functional>
#include <iostream>
#include <string>
#include <thread>
#include <type_traits>
#include <vector>

//-------------------------------------------------------------------------------------------------
//Function class
struct FuncWrapper 
{
    //random data members
    std::string name;
    std::string description;

    std::function<void()> f;

    void operator()() const
    {
        std::cout << "Calling '" << name << "', description = '" << description << "'\n";
        f();
    }
};

//-------------------------------------------------------------------------------------------------

class FuncHolder 
{
public:
    size_t add(const FuncWrapper& wrapper) 
    {
        std::unique_lock<std::mutex> lock{ m_mtx };
        auto id = funcs.size();
        funcs.push_back(wrapper);
        return id;
    }

    const FuncWrapper& get(size_t index) 
    {
        std::unique_lock<std::mutex> lock{ m_mtx };
        return funcs.at(index);
    }

    ~FuncHolder() = default;

    // meyer's singleton, note this design is threadsafe by nature of C  11 or later
    // https://www.modernescpp.com/index.php/thread-safe-initialization-of-a-singleton
    static FuncHolder& Instance()
    {
        static FuncHolder instance;
        return instance;
    }

private:
    FuncHolder() = default;
    std::vector<FuncWrapper> funcs;
    std::mutex m_mtx;
};

//-------------------------------------------------------------------------------------------------
// Data member base class

class Base 
{
public:
    explicit Base(const size_t& id) :
        storedIdx(id)
    {
    }

    //This is meant to be like a constexpr; its unique for each derived type
    inline virtual const std::string& getName() const = 0;

protected:
    //This will be important for a solution I tried
    const size_t storedIdx = 0;
};

//-------------------------------------------------------------------------------------------------

class Derived : 
    public Base 
{
public:
    explicit Derived(const size_t& id) : 
        Base(id),
        m_name{ "Derived" }
    {
        FuncHolder::Instance().add( { "add5", "adds 5", [this] { increment(5); } });
        FuncHolder::Instance().add( { "add1", "adds 1", [this] { increment(1); } });
    }

    inline const std::string& getName() const override 
    {
        return m_name;
    }

    void increment(int amount) 
    {
        m_value  = amount;
    }

private:
    const std::string m_name;
    int m_value  = 0;
};

//-------------------------------------------------------------------------------------------------
//Class to hold the static instances of the different derived type

class BaseHolder 
{
public:
    // meyer's singleton
    static BaseHolder& Instance()
    {
        static BaseHolder instance;
        return instance;
    }
    
    template<typename type_t>
    size_t add()
    {
        static_assert(std::is_base_of_v<Base, type_t>,"type_t is not derived from base");
        auto id = m_objects.size();
        m_objects.push_back(std::make_unique<type_t>(id));
        return id;
    }

    template<typename type_t>
    const type_t* getInstance(size_t id)
    {
        static_assert(std::is_base_of_v<Base, type_t>);
        auto& base_ptr = m_objects.at(id);
        type_t* derived_ptr = dynamic_cast<type_t*>(base_ptr);
        return derived_ptr;
    }

private:
    std::vector<std::unique_ptr<Base>> m_objects;
};


int main() 
{
    // Create an instance of BaseHolder and  objects in itfirst this will ensure
    // that it will live longer then any threads
    auto derived_object_id = BaseHolder::Instance().add<Derived>(); 

    // no need to capture anything (you can also use std::thread here)
    auto future = std::async(std::launch::async, []
    {
       // making a call to Instance() of funcholder is threadsafe
       // the 0 is a bit of a magic number in your desing how would you determine it at runtime?
       auto func = FuncHolder::Instance().get(0);
       func();
    });

    // synchronize
    future.get();
    return 0;
}

轉載請註明出處,本文鏈接:https://www.uj5u.com/gongcheng/354213.html

標籤:c multithreading c 11 lambda this

上一篇:用于加密多個檔案的多執行緒或多處理

下一篇:通過Pool強制退出python多執行緒

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • Git本地庫既關聯GitHub又關聯Gitee

    創建代碼倉庫 使用gitee舉例(github和gitee差不多) 1.在gitee右上角點擊+,選擇新建倉庫 ? 2.選擇填寫倉庫資訊,然后進行創建 ? 3.服務端已經準備好了,本地開始作準備 (1)Git 全域設定 git config --global user.name "成鈺" git c ......

    uj5u.com 2020-09-10 05:04:14 more
  • CODING DevOps 代碼質量實戰系列第二課,相約周三

    隨著 ToB(企業服務)的興起和 ToC(消費互聯網)產品進入成熟期,線上故障帶來的損失越來越大,代碼質量越來越重要,而「質量內建」正是 DevOps 核心理念之一。**《DevOps 代碼質量實戰(PHP 版)》**為 CODING DevOps 代碼質量實戰系列的第二課,同時也是本系列的 PHP ......

    uj5u.com 2020-09-10 05:07:43 more
  • 推薦Scrum書籍

    推薦Scrum書籍 直接上干貨,推薦書籍清單如下(推薦有順序的哦) Scrum指南 Scrum精髓 Scrum敏捷軟體開發 Scrum捷徑 硝煙中的Scrum和XP : 我們如何實施Scrum 敏捷軟體開發:Scrum實戰指南 Scrum要素 大規模Scrum:大規模敏捷組織的設計 用戶故事地圖 用 ......

    uj5u.com 2020-09-10 05:07:45 more
  • CODING DevOps 代碼質量實戰系列最后一課,周四發車

    隨著 ToB(企業服務)的興起和 ToC(消費互聯網)產品進入成熟期,線上故障帶來的損失越來越大,代碼質量越來越重要,而「質量內建」正是 DevOps 核心理念之一。 **《DevOps 代碼質量實戰(Java 版)》**為 CODING DevOps 代碼質量實戰系列的最后一課,同時也是本系列的 ......

    uj5u.com 2020-09-10 05:07:52 more
  • 敏捷軟體工程實踐書籍

    Scrum轉型想要做好,第一步先了解并真正落實Scrum,那么我推薦的Scrum書籍是要看懂并實踐的。第二步是團隊的工程實踐要做扎實。 下面推薦工程實踐書單: 重構:改善既有代碼的設計 決議極限編程 : 擁抱變化 代碼整潔代碼 程式員的職業素養 修改代碼的藝術 撰寫可讀代碼的藝術 測驗驅動開發 : ......

    uj5u.com 2020-09-10 05:07:55 more
  • Jenkins+svn+nginx實作windows環境自動部署vue前端專案

    前面文章介紹了Jenkins+svn+tomcat實作自動化部署,現在終于有空抽時間出來寫下Jenkins+svn+nginx實作自動部署vue前端專案。 jenkins的安裝和配置已經在前面文章進行介紹,下面介紹實作vue前端專案需要進行的哪些額外的步驟。 注意:在安裝jenkins和nginx的 ......

    uj5u.com 2020-09-10 05:08:49 more
  • CODING DevOps 微服務專案實戰系列第一課,明天等你

    CODING DevOps 微服務專案實戰系列第一課**《DevOps 微服務專案實戰:DevOps 初體驗》**將由 CODING DevOps 開發工程師 王寬老師 向大家介紹 DevOps 的基本理念,并探討為什么現代開發活動需要 DevOps,同時將以 eShopOnContainers 項 ......

    uj5u.com 2020-09-10 05:09:14 more
  • CODING DevOps 微服務專案實戰系列第二課來啦!

    近年來,工程專案的結構越來越復雜,需要接入合適的持續集成流水線形式,才能滿足更多變的需求,那么如何優雅地使用 CI 能力提升生產效率呢?CODING DevOps 微服務專案實戰系列第二課 《DevOps 微服務專案實戰:CI 進階用法》 將由 CODING DevOps 全堆疊工程師 何晨哲老師 向 ......

    uj5u.com 2020-09-10 05:09:33 more
  • CODING DevOps 微服務專案實戰系列最后一課,周四開講!

    隨著軟體工程越來越復雜化,如何在 Kubernetes 集群進行灰度發布成為了生產部署的”必修課“,而如何實作安全可控、自動化的灰度發布也成為了持續部署重點關注的問題。CODING DevOps 微服務專案實戰系列最后一課:**《DevOps 微服務專案實戰:基于 Nginx-ingress 的自動 ......

    uj5u.com 2020-09-10 05:10:00 more
  • CODING 儀表盤功能正式推出,實作作業資料可視化!

    CODING 儀表盤功能現已正式推出!該功能旨在用一張張統計卡片的形式,統計并展示使用 CODING 中所產生的資料。這意味著無需額外的設定,就可以收集歸納寶貴的作業資料并予之量化分析。這些海量的資料皆會以圖表或串列的方式躍然紙上,方便團隊成員隨時查看各專案的進度、狀態和指標,云端協作迎來真正意義上 ......

    uj5u.com 2020-09-10 05:11:01 more
最新发布
  • windows系統git使用ssh方式和gitee/github進行同步

    使用git來clone專案有兩種方式:HTTPS和SSH:
    HTTPS:不管是誰,拿到url隨便clone,但是在push的時候需要驗證用戶名和密碼;
    SSH:clone的專案你必須是擁有者或者管理員,而且需要在clone前添加SSH Key。SSH 在push的時候,是不需要輸入用戶名的,如果配置... ......

    uj5u.com 2023-04-19 08:41:12 more
  • windows系統git使用ssh方式和gitee/github進行同步

    使用git來clone專案有兩種方式:HTTPS和SSH:
    HTTPS:不管是誰,拿到url隨便clone,但是在push的時候需要驗證用戶名和密碼;
    SSH:clone的專案你必須是擁有者或者管理員,而且需要在clone前添加SSH Key。SSH 在push的時候,是不需要輸入用戶名的,如果配置... ......

    uj5u.com 2023-04-19 08:35:34 more
  • 2023年農牧行業6大CRM系統、5大場景盤點

    在物聯網、大資料、云計算、人工智能、自動化技術等現代資訊技術蓬勃發展與逐步成熟的背景下,數字化正成為農牧行業供給側結構性變革與高質量發展的核心驅動因素。因此,改造和提升傳統農牧業、開拓創新現代智慧農牧業,加快推進農牧業的現代化、資訊化、數字化建設已成為農牧業發展的重要方向。 當下,企業數字化轉型已經 ......

    uj5u.com 2023-04-18 08:05:44 more
  • 2023年農牧行業6大CRM系統、5大場景盤點

    在物聯網、大資料、云計算、人工智能、自動化技術等現代資訊技術蓬勃發展與逐步成熟的背景下,數字化正成為農牧行業供給側結構性變革與高質量發展的核心驅動因素。因此,改造和提升傳統農牧業、開拓創新現代智慧農牧業,加快推進農牧業的現代化、資訊化、數字化建設已成為農牧業發展的重要方向。 當下,企業數字化轉型已經 ......

    uj5u.com 2023-04-18 08:00:18 more
  • 計算機組成原理—存盤器

    計算機組成原理—硬體結構 二、存盤器 1.概述 存盤器是計算機系統中的記憶設備,用來存放程式和資料 1.1存盤器的層次結構 快取-主存層次主要解決CPU和主存速度不匹配的問題,速度接近快取 主存-輔存層次主要解決存盤系統的容量問題,容量接近與價位接近于主存 2.主存盤器 2.1概述 主存與CPU的聯 ......

    uj5u.com 2023-04-17 08:20:31 more
  • 談一談我對協同開發的一些認識

    如今各互聯網公司普通都使用敏捷開發,采用小步快跑的形式來進行專案開發。如果是小專案或者小需求,那一個開發可能就搞定了。但對于電商等復雜的系統,其功能多,結構復雜,一個人肯定是搞不定的,所以都是很多人來共同開發維護。以我曾經待過的商城團隊為例,光是后端開發就有七十多人。 為了更好地開發這類大型系統,往 ......

    uj5u.com 2023-04-17 08:18:55 more
  • 專案管理PRINCE2核心知識點整理

    PRINCE2,即 PRoject IN Controlled Environment(受控環境中的專案)是一種結構化的專案管理方法論,由英國政府內閣商務部(OGC)推出,是英國專案管理標準。
    PRINCE2 作為一種開放的方法論,是一套結構化的專案管理流程,描述了如何以一種邏輯性的、有組織的方法,... ......

    uj5u.com 2023-04-17 08:18:51 more
  • 談一談我對協同開發的一些認識

    如今各互聯網公司普通都使用敏捷開發,采用小步快跑的形式來進行專案開發。如果是小專案或者小需求,那一個開發可能就搞定了。但對于電商等復雜的系統,其功能多,結構復雜,一個人肯定是搞不定的,所以都是很多人來共同開發維護。以我曾經待過的商城團隊為例,光是后端開發就有七十多人。 為了更好地開發這類大型系統,往 ......

    uj5u.com 2023-04-17 08:18:00 more
  • 專案管理PRINCE2核心知識點整理

    PRINCE2,即 PRoject IN Controlled Environment(受控環境中的專案)是一種結構化的專案管理方法論,由英國政府內閣商務部(OGC)推出,是英國專案管理標準。
    PRINCE2 作為一種開放的方法論,是一套結構化的專案管理流程,描述了如何以一種邏輯性的、有組織的方法,... ......

    uj5u.com 2023-04-17 08:17:55 more
  • 計算機組成原理—存盤器

    計算機組成原理—硬體結構 二、存盤器 1.概述 存盤器是計算機系統中的記憶設備,用來存放程式和資料 1.1存盤器的層次結構 快取-主存層次主要解決CPU和主存速度不匹配的問題,速度接近快取 主存-輔存層次主要解決存盤系統的容量問題,容量接近與價位接近于主存 2.主存盤器 2.1概述 主存與CPU的聯 ......

    uj5u.com 2023-04-17 08:12:06 more