Rust的借用借用体系为啥不能满足线程间共享内存的需求

https://www.bilibili.com/video/BV1HH4y1J7Pf/

我来整理一下您提供的内容,使其更加合理通顺:

多线程的核心是共享内存,即共享变量。多线程的目标是并发地改变这个共享的地址。我们可以用shared_x1和shared_x2来标定这个地址。

这个地址实际上是一个新开辟的内存区域,用于记录u8类型数据的地址。它位于堆(heap)区,相当于内存的二级寻址:先寻找到这个地址,然后通过这个地址再找到实际的值。

由于这个地址在堆上,所以很容易被拷贝。我们需要将这个地址拷贝到线程中。这个地址本身也是一个变量,所以它也有自己的地址。我们必须把这个地址拷贝进来,这样就可以操作u8类型的数据了。

但是,我们需要考虑到线程T1和T2是CPU可以单独处理的两个线程,它们都派生自main线程。main线程、T1线程和T2线程的生命周期是相互独立的,它们都可以提前结束。如果main线程提前释放了共享的内存,而T1和T2还在使用这个地址,就会出现问题。

这就涉及到了生命周期的问题。X(共享的变量)的生命周期必须要在join之后。join不仅确保了当前线程加入到主线程并等待执行结束,还暗含了一种先后关系(happens-before关系)。

为了让X的生命周期更长,我们可以使用Box来分配堆内存。例如:

1
2
3
4
let x = Box::new(0u8);
let x2 = Box::leak(x);
let shared_x1 = &*x2 as *const u8;
let shared_x2 = shared_x1;

这样,我们创建了一个不可修改的堆内存,并通过共享只读指针来访问它。这避免了使用独占锁(独占所有权),因为独占锁不能被多个线程同时使用。

通过这种方式,我们实现了两个线程(T1和T2)对main线程中数据的只读共享。如果需要实现内部可变性,就需要使用互斥锁(Mutex)等同步原语。

Mutex是一个高级的内部可变性实现,它不仅实现了内部可变性,还实现了原子操作。它包含一个守卫(guard),用于自动释放锁,这是一个非常先进的设计。

所有的原子操作,包括Mutex和条件变量(Condvar),都是基于原子变量和内存顺序这两个基本概念。原子变量控制内存顺序,产生了向上或向下的隔离,或者两端的隔离。

需要注意的是,使用Box::leak分配的内存在程序的整个生命周期内都不会被释放。这可能会导致内存泄漏。如果需要在程序结束前释放这块内存,就需要使用unsafe代码,但这就需要程序员自己来确保内存安全。

总的来说,通过这种方法,我们实现了在main函数的堆区申请一个地址,然后在多个线程间共享这个地址的机制。这种方式比在栈上分配内存更灵活,但也带来了更多的复杂性和潜在的内存管理问题。




您说得对,我确实省略了一些内容。我为此道歉,并会尽力重新整理,尽可能保留原文的所有内容。以下是更完整的整理:

多线程的核心是共享内存,即共享变量。我们用u8来共享内存,这是多线程的目标。我们可以用shared_x1和shared_x2来标定这个地址。

这个地址实际上是一个新开辟的内存区域,用于记录u8这个地址的。它位于堆(heap)区,相当于内存的二级寻址:先寻找到这个地址,然后通过这个地址再找到实际的值。

因为这个地址在堆上,所以很容易被拷贝。我们需要将这个地址拷贝到线程中。这个在外面的地址彰显的是一个变量,它存的是一个地址,所以它也有自己的地址。我们必须把这个地址拷贝进来,这个地址拷贝进来后指向了u8,所以就可以操作u8了。

但是,我们需要考虑到线程T1和T2是CPU可以单独处理的两个线程,它们都派生自main线程。main线程、T1线程和T2线程的生命周期是相互独立的,它们都可以提前结束。如果main线程提前释放了共享的内存,而T1和T2还在使用这个地址,就会出现问题。

这就涉及到了生命周期的问题。Rust会报错:”x does not live long enough”,这意味着x缺少一个足够长的生命周期。x的生命周期必须要在join之后。join不仅确保了当前线程加入到主线程并等待执行结束,还暗含了一种先后关系(happens-before关系)。

为了让x的生命周期更长,我们可以使用Box来分配堆内存:

1
2
3
4
let x = Box::new(0u8);
let x2 = Box::leak(x);
let shared_x1 = &*x2 as *const u8;
let shared_x2 = shared_x1;

这样,我们创建了一个不可修改的堆内存,并通过共享只读指针来访问它。这避免了使用独占锁(独占所有权),因为独占锁不能被多个线程同时使用。

通过这种方式,我们实现了两个线程(T1和T2)对main线程中数据的只读共享。如果需要实现内部可变性,就需要使用互斥锁(Mutex)等同步原语。

Mutex是一个高级的内部可变性实现,它不仅实现了内部可变性,还实现了原子操作。它包含一个守卫(guard),用于自动释放锁,这是一个非常先进的设计。

所有的原子操作,包括Mutex和条件变量(Condvar),都是基于原子变量和内存顺序这两个基本概念。原子变量控制内存顺序,产生了向上或向下的隔离,或者两端的隔离。

需要注意的是,使用Box::leak分配的内存在程序的整个生命周期内都不会被释放。这可能会导致内存泄漏。如果需要在程序结束前释放这块内存,就需要使用unsafe代码,但这就需要程序员自己来确保内存安全。

这种方法实现了在main函数的堆区申请一个地址,然后在多个线程间共享这个地址的机制。这种方式比在栈上分配内存更灵活,但也带来了更多的复杂性和潜在的内存管理问题。

堆上的内存分配是不连续的,这可能会导致内存碎片化。这就像在建高速公路时遇到了”钉子户”,这个”钉子户”永远不会拆除,会一直占用这块内存直到程序结束。

如果不想使用unsafe代码,Rust会认为这个地址是不能被销毁的。这意味着程序执行到哪里,这个地址就会一直存在。

最后,我们讨论了如何通过共享指针来实现内部可变性。我们可以使用原子指针和内部可变性来实现这一点,但这可能会导致数据竞争。这就是为什么我们需要使用同步原语如Mutex来确保线程安全。

文章目录