Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Reference Cycle ทำให้หน่วยความจำรั่วได้

การรับประกัน memory safety ของ Rust ทำให้มันยาก แต่ไม่เป็นไปไม่ได้ ที่จะสร้างหน่วยความจำที่ไม่ถูก cleanup โดยบังเอิญ (รู้จักในชื่อ memory leak) การป้องกัน memory leak สมบูรณ์ไม่ใช่หนึ่งในการรับ ประกันของ Rust หมายความว่า memory leak เป็น memory safe ใน Rust เราเห็นได้ว่า Rust อนุญาต memory leak โดยใช้ Rc<T> และ RefCell<T> — เป็นไปได้ที่จะสร้าง reference ที่ item อ้างถึงกันใน cycle นี่สร้าง memory leak เพราะ reference count ของแต่ละ item ใน cycle จะไม่ถึง 0 และค่าจะไม่ถูก drop

สร้าง Reference Cycle

มาดูว่า reference cycle อาจเกิดยังไงและวิธีป้องกัน เริ่มด้วยนิยาม ของ enum List และเมธอด tail ใน Listing 15-25

Filename: src/main.rs
use crate::List::{Cons, Nil};
use std::cell::RefCell;
use std::rc::Rc;

#[derive(Debug)]
enum List {
    Cons(i32, RefCell<Rc<List>>),
    Nil,
}

impl List {
    fn tail(&self) -> Option<&RefCell<Rc<List>>> {
        match self {
            Cons(_, item) => Some(item),
            Nil => None,
        }
    }
}

fn main() {}
Listing 15-25: นิยาม cons list ที่เก็บ RefCell<T> เพื่อให้เราแก้สิ่งที่ variant Cons อ้างถึง

เราใช้รูปแบบอื่นของนิยาม List จาก Listing 15-5 element ที่สองใน variant Cons ตอนนี้เป็น RefCell<Rc<List>> หมายความว่าแทนการมี ความสามารถที่จะแก้ค่า i32 ดังที่เราทำใน Listing 15-24 เราต้องการ แก้ค่า List ที่ variant Cons กำลังชี้ เรายังเพิ่มเมธอด tail เพื่อทำให้สะดวกสำหรับเราในการเข้าถึง item ที่สองถ้าเรามี variant Cons

ใน Listing 15-26 เรากำลังเพิ่มฟังก์ชัน main ที่ใช้นิยามใน Listing 15-25 โค้ดนี้สร้าง list ใน a และ list ใน b ที่ชี้ไปยัง list ใน a จากนั้น มันแก้ list ใน a ให้ชี้ไปยัง b สร้าง reference cycle มี statement println! ระหว่างทางเพื่อแสดงว่า reference count เป็นอะไรที่จุดต่าง ๆ ในกระบวนการนี้

Filename: src/main.rs
use crate::List::{Cons, Nil};
use std::cell::RefCell;
use std::rc::Rc;

#[derive(Debug)]
enum List {
    Cons(i32, RefCell<Rc<List>>),
    Nil,
}

impl List {
    fn tail(&self) -> Option<&RefCell<Rc<List>>> {
        match self {
            Cons(_, item) => Some(item),
            Nil => None,
        }
    }
}

fn main() {
    let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil))));

    println!("a initial rc count = {}", Rc::strong_count(&a));
    println!("a next item = {:?}", a.tail());

    let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a))));

    println!("a rc count after b creation = {}", Rc::strong_count(&a));
    println!("b initial rc count = {}", Rc::strong_count(&b));
    println!("b next item = {:?}", b.tail());

    if let Some(link) = a.tail() {
        *link.borrow_mut() = Rc::clone(&b);
    }

    println!("b rc count after changing a = {}", Rc::strong_count(&b));
    println!("a rc count after changing a = {}", Rc::strong_count(&a));

    // Uncomment the next line to see that we have a cycle;
    // it will overflow the stack.
    // println!("a next item = {:?}", a.tail());
}
Listing 15-26: สร้าง reference cycle ของสองค่า List ที่ชี้ไปหากัน

เราสร้าง instance Rc<List> ที่เก็บค่า List ในตัวแปร a ด้วย list เริ่มต้นของ 5, Nil เราจากนั้นสร้าง instance Rc<List> ที่ เก็บค่า List อื่นในตัวแปร b ที่บรรจุค่า 10 และชี้ไปยัง list ใน a

เราแก้ a ให้มันชี้ไปยัง b แทน Nil สร้าง cycle เราทำสิ่งนั้น โดยใช้เมธอด tail เพื่อรับ reference ของ RefCell<Rc<List>> ใน a ที่เราใส่ในตัวแปร link จากนั้น เราใช้เมธอด borrow_mut บน RefCell<Rc<List>> เพื่อเปลี่ยนค่าภายในจาก Rc<List> ที่เก็บค่า Nil เป็น Rc<List> ใน b

เมื่อเรารันโค้ดนี้ เก็บ println! สุดท้ายเป็น comment ตอนนี้ เรา จะได้ output นี้:

$ cargo run
   Compiling cons-list v0.1.0 (file:///projects/cons-list)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.53s
     Running `target/debug/cons-list`
a initial rc count = 1
a next item = Some(RefCell { value: Nil })
a rc count after b creation = 2
b initial rc count = 1
b next item = Some(RefCell { value: Cons(5, RefCell { value: Nil }) })
b rc count after changing a = 2
a rc count after changing a = 2

reference count ของ instance Rc<List> ในทั้ง a และ b เป็น 2 หลังจากที่เราเปลี่ยน list ใน a ให้ชี้ไปยัง b ที่ท้ายสุดของ main Rust drop ตัวแปร b ซึ่งลด reference count ของ instance Rc<List> b จาก 2 เป็น 1 memory ที่ Rc<List> มีบน heap จะไม่ ถูก drop ที่จุดนี้เพราะ reference count ของมันคือ 1 ไม่ใช่ 0 จากนั้น Rust drop a ซึ่งลด reference count ของ instance Rc<List> a จาก 2 เป็น 1 ด้วย memory ของ instance นี้ก็ drop ไม่ได้ เพราะ instance Rc<List> อื่นยังอ้างถึงมัน memory ที่ allocate ให้ list จะยังไม่ถูกเก็บตลอดไป เพื่อ visualize reference cycle นี้ เราได้ สร้าง diagram ใน Figure 15-4

A rectangle labeled 'a' that points to a rectangle containing the integer 5. A rectangle labeled 'b' that points to a rectangle containing the integer 10. The rectangle containing 5 points to the rectangle containing 10, and the rectangle containing 10 points back to the rectangle containing 5, creating a cycle.

Figure 15-4: reference cycle ของ list a และ b ที่ชี้ไปหากัน

ถ้าคุณ uncomment println! สุดท้ายและรันโปรแกรม Rust จะพยายาม print cycle นี้ด้วย a ชี้ไปยัง b ชี้ไปยัง a และเช่นนั้นต่อไปจนกว่ามัน overflow stack

เปรียบเทียบกับโปรแกรมในโลกจริง ผลของการสร้าง reference cycle ใน ตัวอย่างนี้ไม่รุนแรงมาก — หลังจากเราสร้าง reference cycle โปรแกรม จบ อย่างไรก็ตาม ถ้าโปรแกรมที่ซับซ้อนกว่า allocate memory เยอะใน cycle และเก็บมันเป็นเวลานาน โปรแกรมจะใช้ memory มากกว่าที่ต้องการ และอาจ overwhelm ระบบ ทำให้มันใช้ memory ที่มีหมด

การสร้าง reference cycle ไม่ทำง่าย แต่ก็ไม่เป็นไปไม่ได้ ถ้าคุณมี ค่า RefCell<T> ที่บรรจุค่า Rc<T> หรือการรวมประเภทซ้อนคล้ายกัน ของ type ที่มี interior mutability และ reference counting คุณต้อง รับประกันว่าคุณไม่สร้าง cycle — คุณพึ่ง Rust ในการจับพวกมันไม่ได้ การสร้าง reference cycle จะเป็น bug logic ในโปรแกรมของคุณที่คุณควร ใช้ automated test, code review และการปฏิบัติพัฒนาซอฟต์แวร์อื่นเพื่อ ลด

วิธีแก้อื่นสำหรับการหลีกเลี่ยง reference cycle คือการจัดระเบียบ โครงสร้างข้อมูลของคุณใหม่เพื่อให้บาง reference แสดง ownership และ บาง reference ไม่ ผลคือ คุณมี cycle ที่ประกอบด้วยความสัมพันธ์ ownership บางอย่างและความสัมพันธ์ไม่ใช่ ownership บางอย่างได้ และ เฉพาะความสัมพันธ์ ownership กระทบว่าค่า drop ได้หรือไม่ ใน Listing 15-25 เราต้องการ variant Cons เสมอที่จะ own list ของพวกมัน ดังนั้น การจัดระเบียบโครงสร้างข้อมูลใหม่เป็นไปไม่ได้ มาดูตัวอย่างโดยใช้ graph ที่ประกอบด้วย node parent และ node child เพื่อเห็นเมื่อ ความสัมพันธ์ไม่ใช่ ownership เป็นวิธีที่เหมาะสมในการป้องกัน reference cycle

ป้องกัน Reference Cycle โดยใช้ Weak<T>

จนถึงตอนนี้ เราได้สาธิตว่าการเรียก Rc::clone เพิ่ม strong_count ของ instance Rc<T> และ instance Rc<T> ถูก cleanup เฉพาะถ้า strong_count ของมันคือ 0 คุณยังสร้าง weak reference ของค่าภายใน instance Rc<T> ได้โดยเรียก Rc::downgrade และส่ง reference ของ Rc<T> Strong reference คือวิธีที่คุณแชร์ ownership ของ instance Rc<T> Weak reference ไม่แสดงความสัมพันธ์ ownership และ count ของพวกมันไม่กระทบเมื่อ instance Rc<T> ถูก cleanup พวกมันจะไม่ทำ ให้เกิด reference cycle เพราะ cycle ใดที่เกี่ยวข้อง weak reference บางตัวจะถูกตัดเมื่อ strong reference count ของค่าที่เกี่ยวข้องเป็น 0

เมื่อคุณเรียก Rc::downgrade คุณได้ smart pointer type Weak<T> แทนการเพิ่ม strong_count ใน instance Rc<T> ขึ้น 1 การเรียก Rc::downgrade เพิ่ม weak_count ขึ้น 1 type Rc<T> ใช้ weak_count เพื่อตามว่ามี reference Weak<T> เท่าไรที่มีอยู่ คล้ายกับ strong_count ความแตกต่างคือ weak_count ไม่ต้องเป็น 0 สำหรับ instance Rc<T> ที่จะถูก cleanup

เพราะค่าที่ Weak<T> reference อาจถูก drop เพื่อทำอะไรกับค่าที่ Weak<T> กำลังชี้ คุณต้องรับประกันว่าค่ายังมีอยู่ ทำสิ่งนี้โดย เรียกเมธอด upgrade บน instance Weak<T> ซึ่งจะ return Option<Rc<T>> คุณจะได้ผลของ Some ถ้าค่า Rc<T> ยังไม่ถูก drop และผลของ None ถ้าค่า Rc<T> ถูก drop แล้ว เพราะ upgrade return Option<Rc<T>> Rust จะรับประกันว่ากรณี Some และกรณี None ถูกจัดการ และจะไม่มี pointer ที่ไม่ valid

เป็นตัวอย่าง แทนการใช้ list ที่ item รู้เฉพาะเกี่ยวกับ item ถัดไป เราจะสร้างต้นไม้ที่ item รู้เกี่ยวกับ item child ของพวกมัน_และ_ item parent

สร้างโครงสร้างข้อมูลต้นไม้

เพื่อเริ่ม เราจะ build ต้นไม้ที่ node รู้เกี่ยวกับ node child ของ พวกมัน เราจะสร้าง struct ชื่อ Node ที่เก็บค่า i32 ของตัวเอง รวมถึง reference ของค่า Node child ของมัน:

Filename: src/main.rs

use std::cell::RefCell;
use std::rc::Rc;

#[derive(Debug)]
struct Node {
    value: i32,
    children: RefCell<Vec<Rc<Node>>>,
}

fn main() {
    let leaf = Rc::new(Node {
        value: 3,
        children: RefCell::new(vec![]),
    });

    let branch = Rc::new(Node {
        value: 5,
        children: RefCell::new(vec![Rc::clone(&leaf)]),
    });
}

เราต้องการให้ Node own children ของมัน และเราต้องการแชร์ ownership นั้นกับตัวแปรเพื่อให้เราเข้าถึงแต่ละ Node ในต้นไม้โดยตรงได้ เพื่อ ทำสิ่งนี้ เรานิยาม item Vec<T> ให้เป็นค่าของ type Rc<Node> เรา ยังต้องการแก้ว่า node ไหนเป็น child ของอีก node ดังนั้นเรามี RefCell<T> ใน children รอบ Vec<Rc<Node>>

ถัดไป เราจะใช้นิยาม struct ของเราและสร้างหนึ่ง instance Node ชื่อ leaf ที่มีค่า 3 และไม่มี children และอีก instance ชื่อ branch ที่มีค่า 5 และ leaf เป็นหนึ่งใน children ของมัน ดังที่แสดงใน Listing 15-27

Filename: src/main.rs
use std::cell::RefCell;
use std::rc::Rc;

#[derive(Debug)]
struct Node {
    value: i32,
    children: RefCell<Vec<Rc<Node>>>,
}

fn main() {
    let leaf = Rc::new(Node {
        value: 3,
        children: RefCell::new(vec![]),
    });

    let branch = Rc::new(Node {
        value: 5,
        children: RefCell::new(vec![Rc::clone(&leaf)]),
    });
}
Listing 15-27: สร้าง node leaf ที่ไม่มี children และ node branch ที่มี leaf เป็นหนึ่งใน children

เรา clone Rc<Node> ใน leaf และเก็บนั้นใน branch หมายความว่า Node ใน leaf ตอนนี้มีสอง owner — leaf และ branch เราไปจาก branch ไปยัง leaf ผ่าน branch.children ได้ แต่ไม่มีวิธีไปจาก leaf ไปยัง branch เหตุผลคือ leaf ไม่มี reference ของ branch และไม่รู้ว่าพวกมันเกี่ยวข้องกัน เราต้องการให้ leaf รู้ว่า branch เป็น parent ของมัน เราจะทำสิ่งนั้นต่อไป

เพิ่ม Reference จาก Child ไปยัง Parent

เพื่อทำให้ node child รู้เกี่ยวกับ parent ของมัน เราต้องเพิ่ม field parent ในนิยาม struct Node ของเรา ปัญหาคือในการตัดสินว่า type ของ parent ควรเป็นอะไร เรารู้ว่ามันบรรจุ Rc<T> ไม่ได้ เพราะนั่น จะสร้าง reference cycle กับ leaf.parent ชี้ไปยัง branch และ branch.children ชี้ไปยัง leaf ซึ่งจะทำให้ค่า strong_count ของพวกมันไม่เป็น 0

คิดถึงความสัมพันธ์ในอีกแบบ node parent ควร own children ของมัน — ถ้า node parent ถูก drop, node child ของมันควรถูก drop ด้วย อย่าง ไรก็ตาม child ไม่ควร own parent ของมัน — ถ้าเรา drop node child parent ควรยังมีอยู่ นี่คือกรณีสำหรับ weak reference!

ดังนั้น แทน Rc<T> เราจะทำให้ type ของ parent ใช้ Weak<T> เจาะจง RefCell<Weak<Node>> ตอนนี้นิยาม struct Node ของเราดูแบบ นี้:

Filename: src/main.rs

use std::cell::RefCell;
use std::rc::{Rc, Weak};

#[derive(Debug)]
struct Node {
    value: i32,
    parent: RefCell<Weak<Node>>,
    children: RefCell<Vec<Rc<Node>>>,
}

fn main() {
    let leaf = Rc::new(Node {
        value: 3,
        parent: RefCell::new(Weak::new()),
        children: RefCell::new(vec![]),
    });

    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());

    let branch = Rc::new(Node {
        value: 5,
        parent: RefCell::new(Weak::new()),
        children: RefCell::new(vec![Rc::clone(&leaf)]),
    });

    *leaf.parent.borrow_mut() = Rc::downgrade(&branch);

    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
}

node จะสามารถอ้างถึง node parent ของมัน แต่ไม่ own parent ใน Listing 15-28 เราอัพเดท main ให้ใช้นิยามใหม่นี้เพื่อให้ node leaf จะมีวิธีอ้างถึง parent ของมัน branch

Filename: src/main.rs
use std::cell::RefCell;
use std::rc::{Rc, Weak};

#[derive(Debug)]
struct Node {
    value: i32,
    parent: RefCell<Weak<Node>>,
    children: RefCell<Vec<Rc<Node>>>,
}

fn main() {
    let leaf = Rc::new(Node {
        value: 3,
        parent: RefCell::new(Weak::new()),
        children: RefCell::new(vec![]),
    });

    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());

    let branch = Rc::new(Node {
        value: 5,
        parent: RefCell::new(Weak::new()),
        children: RefCell::new(vec![Rc::clone(&leaf)]),
    });

    *leaf.parent.borrow_mut() = Rc::downgrade(&branch);

    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
}
Listing 15-28: node leaf ที่มี weak reference ของ node parent ของมัน branch

การสร้าง node leaf ดูคล้ายกับ Listing 15-27 ยกเว้น field parentleaf เริ่มต้นโดยไม่มี parent ดังนั้นเราสร้าง instance reference Weak<Node> ใหม่ว่าง

ที่จุดนี้ เมื่อเราพยายามรับ reference ของ parent ของ leaf โดยใช้ เมธอด upgrade เราได้ค่า None เราเห็นนี้ใน output จาก statement println! แรก:

leaf parent = None

เมื่อเราสร้าง node branch มันจะมี reference Weak<Node> ใหม่ใน field parent ด้วยเพราะ branch ไม่มี node parent เรายังมี leaf เป็นหนึ่งใน children ของ branch เมื่อเรามี instance Node ใน branch แล้ว เราแก้ leaf ให้มัน reference Weak<Node> ของ parent ของมันได้ เราใช้เมธอด borrow_mut บน RefCell<Weak<Node>> ใน field parent ของ leaf และจากนั้นเราใช้ฟังก์ชัน Rc::downgrade เพื่อสร้าง reference Weak<Node> ของ branch จาก Rc<Node> ใน branch

เมื่อเรา print parent ของ leaf อีก คราวนี้เราจะได้ variant Some ที่เก็บ branch — ตอนนี้ leaf เข้าถึง parent ของมันได้! เมื่อ เรา print leaf เรายังหลีกเลี่ยง cycle ที่ในที่สุดจบใน stack overflow เหมือนที่เรามีใน Listing 15-26 — reference Weak<Node> ถูก print เป็น (Weak):

leaf parent = Some(Node { value: 5, parent: RefCell { value: (Weak) },
children: RefCell { value: [Node { value: 3, parent: RefCell { value: (Weak) },
children: RefCell { value: [] } }] } })

การขาด output ไม่สิ้นสุดระบุว่าโค้ดนี้ไม่ได้สร้าง reference cycle เรายังบอกนี้โดยดูค่าที่เราได้จากการเรียก Rc::strong_count และ Rc::weak_count

Visualize การเปลี่ยนแปลง strong_count และ weak_count

มาดูว่าค่า strong_count และ weak_count ของ instance Rc<Node> เปลี่ยนยังไงโดยสร้าง scope ภายในใหม่และย้ายการสร้าง branch เข้า scope นั้น โดยทำเช่นนั้น เราเห็นสิ่งที่เกิดเมื่อ branch ถูกสร้าง และจากนั้น drop เมื่อมันออกจาก scope ได้ การแก้ถูกแสดงใน Listing 15-29

Filename: src/main.rs
use std::cell::RefCell;
use std::rc::{Rc, Weak};

#[derive(Debug)]
struct Node {
    value: i32,
    parent: RefCell<Weak<Node>>,
    children: RefCell<Vec<Rc<Node>>>,
}

fn main() {
    let leaf = Rc::new(Node {
        value: 3,
        parent: RefCell::new(Weak::new()),
        children: RefCell::new(vec![]),
    });

    println!(
        "leaf strong = {}, weak = {}",
        Rc::strong_count(&leaf),
        Rc::weak_count(&leaf),
    );

    {
        let branch = Rc::new(Node {
            value: 5,
            parent: RefCell::new(Weak::new()),
            children: RefCell::new(vec![Rc::clone(&leaf)]),
        });

        *leaf.parent.borrow_mut() = Rc::downgrade(&branch);

        println!(
            "branch strong = {}, weak = {}",
            Rc::strong_count(&branch),
            Rc::weak_count(&branch),
        );

        println!(
            "leaf strong = {}, weak = {}",
            Rc::strong_count(&leaf),
            Rc::weak_count(&leaf),
        );
    }

    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
    println!(
        "leaf strong = {}, weak = {}",
        Rc::strong_count(&leaf),
        Rc::weak_count(&leaf),
    );
}
Listing 15-29: สร้าง branch ใน scope ภายในและตรวจสอบ strong และ weak reference count

หลังจาก leaf ถูกสร้าง Rc<Node> ของมันมี strong count 1 และ weak count 0 ใน scope ภายใน เราสร้าง branch และ associate มันกับ leaf ที่จุดที่เมื่อเรา print count Rc<Node> ใน branch จะมี strong count 1 และ weak count 1 (สำหรับ leaf.parent ชี้ไปยัง branch ด้วย Weak<Node>) เมื่อเรา print count ใน leaf เราจะ เห็นมันจะมี strong count 2 เพราะ branch ตอนนี้มี clone ของ Rc<Node> ของ leaf ที่เก็บใน branch.children แต่จะยังมี weak count 0

เมื่อ scope ภายในจบ branch ออกจาก scope และ strong count ของ Rc<Node> ลดเป็น 0 ดังนั้น Node ของมันถูก drop weak count 1 จาก leaf.parent ไม่มีผลต่อว่า Node ถูก drop หรือไม่ ดังนั้นเรา ไม่ได้ memory leak ใด!

ถ้าเราพยายามเข้าถึง parent ของ leaf หลังจากท้ายสุดของ scope เรา จะได้ None อีก ที่ท้ายสุดของโปรแกรม Rc<Node> ใน leaf มี strong count 1 และ weak count 0 เพราะตัวแปร leaf ตอนนี้เป็น reference เดียวของ Rc<Node> อีก

logic ทั้งหมดที่จัดการ count และการ drop ค่าถูก built เข้าใน Rc<T> และ Weak<T> และ implementation trait Drop ของพวกมัน โดยระบุว่าความสัมพันธ์จาก child ไปยัง parent ของมันควรเป็น reference Weak<T> ในนิยามของ Node คุณสามารถมี node parent ชี้ไปยัง node child และในทางกลับกันโดยไม่สร้าง reference cycle และ memory leak

สรุป

บทนี้ครอบคลุมวิธีใช้ smart pointer เพื่อทำการรับประกันและ trade-off ต่างจากที่ Rust ทำเป็นค่าเริ่มต้นกับ reference ปกติ type Box<T> มีขนาดที่รู้และชี้ไปยังข้อมูลที่ allocate บน heap type Rc<T> ตาม จำนวน reference ของข้อมูลบน heap เพื่อให้ข้อมูลมี owner หลายตัวได้ type RefCell<T> ด้วย interior mutability ของมันให้เรา type ที่ เราใช้ได้เมื่อเราต้องการ type ที่ immutable แต่ต้องเปลี่ยนค่าภายใน ของ type นั้น มันยังบังคับใช้กฎ borrowing ที่ runtime ไม่ใช่ที่ compile time

ยังพูดถึง trait Deref และ Drop ซึ่งเปิดใช้ functionality ของ smart pointer มาก เราสำรวจ reference cycle ที่ก่อให้เกิด memory leak ได้และวิธีป้องกันพวกมันโดยใช้ Weak<T>

ถ้าบทนี้กระตุ้นความสนใจของคุณและคุณต้องการ implement smart pointer ของคุณเอง ดู “The Rustonomicon” สำหรับข้อมูลที่มีประโยชน์ เพิ่ม

ถัดไป เราจะพูดถึง concurrency ใน Rust คุณจะเรียนเกี่ยวกับ smart pointer ใหม่บางตัวด้วย