精华内容
下载资源
问答
  • 假设单核CPU情况下。(既高速缓存都一样) 一 一个变量a被线程A和线程...还是这个例子,假设A线程和B线程共享主存数据a变量,A、B线程读入到CPU缓存后,到底一个缓存呢?还是各自线程看到缓存不一样
  • 根据他,两个线程不一定共享静态变量,特别在每个线程(主线程对ReaderThread)在其自己处理器上运行并且因此不共享相同的寄存器/高速缓存等等情况下, CPU不会更新其他。基本上,他说可能ready在主线程更新,...

    我的老师在一个上层java类线程上说的东西,我不知道。

    他说,以下代码不一定会更新ready变量。根据他,两个线程不一定共享静态变量,特别是在每个线程(主线程对ReaderThread)在其自己的处理器上运行并且因此不共享相同的寄存器/高速缓存等等的情况下, CPU不会更新其他。

    基本上,他说可能是ready在主线程更新,但不是在ReaderThread,使ReaderThread将无限循环。他还声称程序可以打印’0’或’42’。我知道如何打印’42’,但不是’0’。他提到,当数字变量设置为默认值时就是这种情况。

    我想也许不能保证静态变量在线程之间更新,但这让我对Java很奇怪。做好准备挥发性纠正这个问题吗?

    他显示了这段代码:

    public class NoVisibility {

    private static boolean ready;

    private static int number;

    private static class ReaderThread extends Thread {

    public void run() {

    while (!ready) Thread.yield();

    System.out.println(number);

    }

    }

    public static void main(String[] args) {

    new ReaderThread().start();

    number = 42;

    ready = true;

    }

    }

    展开全文
  • 记住了不一定真懂关于这个问题有同学可能已经“背得”滚瓜烂熟了:“进程操作系统分配资源单位,线程是调度基本单位,线程之间共享进程资源”。可是你真理解了上面最后一句话吗?到底线程之间共享了哪些...

    8bc17c95d204495edd1a4fa3fee38c13.png

    前言

    进程和线程这两个话题是程序员绕不开的,操作系统提供的这两个抽象概念实在是太重要了。关于进程和线程有一个极其经典的问题,那就是进程和线程的区别是什么?相信很多同学对答案似懂非懂。

    记住了不一定真懂

    关于这个问题有的同学可能已经“背得”滚瓜烂熟了:“进程是操作系统分配资源的单位,线程是调度的基本单位,线程之间共享进程资源”。可是你真的理解了上面最后一句话吗?到底线程之间共享了哪些进程资源,共享资源意味着什么?共享资源这种机制是如何实现的?对此如果你没有答案的话,那么这意味着你几乎很难写出能正确工作的多线程程序,同时也意味着这篇文章就是为你准备的。

    逆向思考

    查理芒格经常说这样一句话:“反过来想,总是反过来想”,如果你对线程之间共享了哪些进程资源这个问题想不清楚的话那么也可以反过来思考,那就是有哪些资源是线程私有的

    线程私有资源

    线程运行的本质其实就是函数的执行,函数的执行总会有一个源头,这个源头就是所谓的入口函数,CPU从入口函数开始执行从而形成一个执行流,只不过我们人为的给执行流起一个名字,这个名字就叫线程。

    既然线程运行的本质就是函数的执行,那么函数执行都有哪些信息呢?在《[函数运行时在内存中是什么样子]》这篇文章中应该提过,函数运行时的信息保存在栈帧中,栈帧中保存了函数的返回值、调用其它函数的参数、该函数使用的局部变量以及该函数使用的寄存器信。

    如图所示,假设函数A调用函数B:

    786afbd606d57965d262e73d26ceb855.png

    此外,CPU执行指令的信息保存在一个叫做程序计数器的寄存器中,通过这个寄存器我们就知道接下来要执行哪一条指令。由于操作系统随时可以暂停线程的运行,因此我们保存以及恢复程序计数器中的值就能知道线程是从哪里暂停的以及该从哪里继续运行了。由于线程运行的本质就是函数运行,函数运行时信息是保存在栈帧中的,因此每个线程都有自己独立的、私有的栈区。

    b2f7445a94d8b91afe1cfdd77cdd2db9.png


    图片

    同时函数运行时需要额外的寄存器来保存一些信息,像部分局部变量之类,这些寄存器也是线程私有的,一个线程不可能访问到另一个线程的这类寄存器信息

    从上面的讨论中我们知道,到目前为止,所属线程的栈区、程序计数器、栈指针以及函数运行使用的寄存器是线程私有的。

    以上这些信息有一个统一的名字,就是线程上下文,thread context。我们也说过操作系统调度线程需要随时中断线程的运行并且需要线程被暂停后可以继续运行,操作系统之所以能实现这一点,依靠的就是线程上下文信息。

    现在你应该知道哪些是线程私有的了吧。除此之外,剩下的都是线程间共享资源。那么剩下的还有什么呢?还有图中的这些。

    e56a99d33379c460a2024745cb7748fc.png

    这其实就是进程地址空间的样子,也就是说线程共享进程地址空间中除线程上下文信息中的所有内容,意思就是说线程可以直接读取这些内容。接下来我们分别来看一下这些区域。

    代码区

    进程地址空间中的代码区,这里保存的是什么呢?从名字中有的同学可能已经猜到了,没错,这里保存的就是我们写的代码,更准确的是编译后的可执行机器指令

    那么这些机器指令又是从哪里来的呢?答案是从可执行文件中加载到内存的,可执行程序中的代码区就是用来初始化进程地址空间中的代码区的。

    3d95bdad77d0b3ce26a0ac46eb31dce6.png

    线程之间共享代码区,这就意味着程序中的任何一个函数都可以放到线程中去执行,不存在某个函数只能被特定线程执行的情况

    数据区

    进程地址空间中的数据区,这里存放的就是所谓的全局变量。什么是全局变量?所谓全局变量就是那些你定义在函数之外的变量,在C语言中就像这样:

    dbdbe9695cb82c8e82f6cba6044b8f5b.png

    其中字符c就是全局变量,存放在进程地址空间中的数据区。

    f37425ec06fe89d7c9d120c02d500ce9.png

    在程序员运行期间,也就是run time,数据区中的全局变量有且仅有一个实例,所有的线程都可以访问到该全局变量。值得注意的是,在C语言中还有一类特殊的“全局变量”,那就是用static关键词修饰过的变量,就像这样:

    2b1a4563504e68fefc757ea4cee3b168.png

    注意到,虽然变量a定义在函数内部,但变量a依然具有全局变量的特性,也就是说变量a放在了进程地址空间的数据区域,即使函数执行完后该变量依然存在,而普通的局部变量随着函数调用结束和函数栈帧一起被回收掉了,但这里的变量a不会被回收,因为其被放到了数据区。
    这样的变量对每个线程来说也是可见的,也就是说每个线程都可以访问到该变量。

    堆区

    堆区是程序员比较熟悉的,我们在C/C++中用malloc或者new出来的数据就存放在这个区域,很显然,只要知道变量的地址,也就是指针,任何一个线程都可以访问指针指向的数据,因此堆区也是线程共享的属于进程的资源。

    85199aa24c3a6fa554e43e556e664451.png

    栈区

    唉,等等!刚不是说栈区是线程私有资源吗,怎么这会儿又说起栈区了?确实,从线程这个抽象的概念上来说,栈区是线程私有的,然而从实际的实现上看,栈区属于线程私有这一规则并没有严格遵守

    这句话是什么意思?

    通常来说,注意这里的用词是通常,通常来说栈区是线程私有,既然有通常就有不通常的时候。不通常是因为不像进程地址空间之间的严格隔离,线程的栈区没有严格的隔离机制来保护

    因此如果一个线程能拿到来自另一个线程栈帧上的指针,那么该线程就可以改变另一个线程的栈区,也就是说这些线程可以任意修改本属于另一个线程栈区中的变量。

    e1b9ed729965215112cefc23c14bec31.png


    图片

    这从某种程度上给了程序员极大的便利,但同时,这也会导致极其难以排查到的bug。

    试想一下你的程序运行的好好的,结果某个时刻突然出问题,定位到出问题代码行后根本就排查不到原因,你当然是排查不到问题原因的,因为你的程序本来就没有任何问题。

    是别人的问题导致你的函数栈帧数据被写坏从而产生bug,这样的问题通常很难排查到原因,需要对整体的项目代码非常熟悉,常用的一些debug工具这时可能已经没有多大作用了。

    说了这么多,那么同学可能会问,一个线程是怎样修改本属于其它线程的数据呢?接下来我们用一个代码示例讲解一下。

    修改线程私有数据

    不要担心,以下代码足够简单:

    void thread(void* var) {

    这段代码是什么意思呢?

    首先我们在主线程的栈区定义了一个局部变量,也就是 int a= 1这行代码,现在我们已经知道了,局部变量a属于主线程私有数据,但是,接下来我们创建了另外一个线程。

    在新创建的这个线程中,我们将变量a的地址以参数的形式传给了新创建的线程,然后我来看一下thread函数。在新创建的线程中,我们获取到了变量a的指针,然后将其修改为了2

    也就是这行代码,我们在新创建的线程中修改了本属于主线程的私有数据。

    a8972f5061b5d0e5f9d6e1bc31c6e8ef.png

    现在你应该看明白了吧,尽管栈区是线程的私有数据,但由于栈区没有添加任何保护机制,一个线程的栈区对其它线程是可以见的,也就是说我们可以修改属于任何一个线程的栈区。

    就像我们上文说得到的,这给程序员带来了极大便利的同时也带来了无尽的麻烦,试想上面这段代码,如果确实是项目需要那么这样写代码无可厚非。

    但如果上述新创建线程是因bug修改了属于其它线程的私有数据的话,那么产生问题就很难定位了,因为bug可能距离问题暴露的这行代码已经很远了,这样的问题通常难以排查。

    动态链接库

    进程地址空间中除了以上讨论的这些实际上还有其它内容,还有什么呢?这就要从可执行程序说起了。

    什么是可执行程序呢?

    在Windows中就是我们熟悉的exe文件,在Linux世界中就是ELF文件,这些可以被操作系统直接运行的程序就是我们所说的可执行程序。

    那么可执行程序是怎么来的呢?有的同学可能会说,废话,不就是编译器生成的吗?实际上这个答案只答对了一半。

    假设我们的项目比较简单只有几个源码文件,编译器是怎么把这几个源代码文件转换为最终的一个可执行程序呢?

    原来,编译器在将可执行程序翻译成机器指令后,接下来还有一个重要的步骤,这就是链接,链接完成后生成的才是可执行程序。完成链接这一过程的就是链接器。

    da332f1c528349fe5dd824132ba09fc8.png

    其中链接器可以有两种链接方式,这就是静态链接动态链接。静态链接的意思是说把所有的机器指令一股脑全部打包到可执行程序中,动态链接的意思是我们不把动态链接的部分打包到可执行程序,而是在可执行程序运行起来后去内存中找动态链接的那部分代码,这就是所谓的静态链接和动态链接。

    动态链接一个显而易见的好处就是可执行程序的大小会很小,就像我们在Windows下看一个exe文件可能很小,那么该exe很可能是动态链接的方式生成的

    而动态链接的部分生成的库就是我们熟悉的动态链接库,在Windows下是以DLL结尾的文件,在Linux下是以so结尾的文件。说了这么多,这和线程共享资源有什么关系呢?

    原来如果一个程序是动态链接生成的,那么其地址空间中有一部分包含的就是动态链接库,否则程序就运行不起来了,这一部分的地址空间也是被所有线程所共享的。

    80d14f6bf712608aad04e89250df7b60.png

    也就是说进程中的所有线程都可以使用动态链接库中的代码。以上其实是关于链接这一主题的极简介绍,关于链接这一话题的详细讨论可以参考《[彻底理解链接器]》系列文章。

    文件

    最后,如果程序在运行过程中打开了一些文件,那么进程地址空间中还保存有打开的文件信息,进程打开的文件也可以被所有的线程使用,这也属于线程间的共享资源。

    1e89e0b336cc7bb3773fd916635a22d2.png

    ** One More Thing:TLS**

    本文就这些了吗?实际上关于线程私有数据还有一项没有详细讲解,因为再讲下去本篇就撑爆了,而且本篇已经讲解的部分足够用了,剩下的这一点仅仅作为补充,也就是选学部分,如果你对此不感兴趣的话完全可以跳过,没有问题

    。关于线程私有数据还有一项技术,那就是线程局部存储,Thread Local Storage,TLS。这是什么意思呢?其实从名字上也可以看出,所谓线程局部存储,是指存放在该区域中的变量有两个含义:

    • 存放在该区域中的变量是全局变量,所有线程都可以访问
    • 虽然看上去所有线程访问的都是同一个变量,但该全局变量独属于一个线程,一个线程对此变量的修改对其他线程不可见。

    说了这么多还是没懂有没有?没关系,接下来看完这两段代码还不懂你来打我。我们先来看第一段代码,不用担心,这段代码非常非常的简单:

    int a = 1; // 全局变量

    怎么样,这段代码足够简单吧,上述代码是用C++11写的,我来讲解下这段代码是什么意思。

    • 首先我们创建了一个全局变量a,初始值为1
    • 其次我们创建了两个线程,每个线程对变量a加1
    • 线程的join函数表示该线程运行完毕后才继续运行接下来的代码

    那么这段代码的运行起来会打印什么呢?全局变量a的初始值为1,第一个线程加1后a变为2,因此会打印2;第二个线程再次加1后a变为3,因此会打印3,让我们来看一下运行结果:

    2

    看来我们分析的没错,全局变量在两个线程分别加1后最终变为3。接下来我们对变量a的定义稍作修改,其它代码不做改动:

    __thread int a = 1; // 线程局部存储

    我们看到全局变量a前面加了一个__thread关键词用来修饰,也就是说我们告诉编译器把变量a放在线程局部存储中,那这会对程序带来哪些改变呢?简单运行一下就知道了:

    2

    和你想的一样吗?有的同学可能会大吃一惊,为什么我们明明对变量a加了两次,但第二次运行为什么还是打印2而不是3呢?

    想一想这是为什么。原来,这就是线程局部存储的作用所在,线程t1对变量a的修改不会影响到线程t2,线程t1在将变量a加到1后变为2,但对于线程t2来说此时变量a依然是1,因此加1后依然是2。

    因此,线程局部存储可以让你使用一个独属于线程的全局变量。也就是说,虽然该变量可以被所有线程访问,但该变量在每个线程中都有一个副本,一个线程对改变量的修改不会影响到其它线程。

    c8a72b84b74f8a3081dc96f2e33f5006.png

    总结

    怎么样,没想到教科书上一句简单的“线程共享进程资源”背后竟然会有这么多的知识点吧,教科书上的知识看似容易,但,并不简单。希望本篇能对大家理解进程、线程能有多帮助。最后的最后,如果觉得文章对你有帮助的话,请多多分享一下!!!

    展开全文
  • i++不安全,前面我们讲解volatile关键字时候,我们说过了i++一个复合操作,可分为三个阶段: ...如果静态成员变量,i++则不是线程安全,因为 线程共享栈区,不共享堆区和全局区 如何解决线...

    i++是不安全的,前面我们讲解volatile关键字的时候,我们说过了i++是一个复合操作,可分为三个阶段:
    读值,从内存到寄存器
    +1,寄存器自增
    写值,写回内存
    在这三步之间的都可能会有CPU调度,造成i的值被修改。造成脏读脏写。

    如果是方法里定义的,一定是线程安全的,因为每个方法栈是线程私有的;如果是类的静态成员变量,i++则不是线程安全的,因为 线程共享栈区,不共享堆区和全局区

    如何解决线程安全性呢?
    用volatile修饰虽然能保证可见性,但是不能保证原子性。如果想要保证其多线程下的安全性,可以使用原子变量(AtomicInteger,参考 Java并发编程之原子变量)、
    sychronized关键字、Lock锁实现(参考 Java关键字 volatile、synchronized和Lock锁)。

    AtomicInteger 和 各种 Lock 都可以确保线程安全。AtomicInteger 的效率高是因为它是互斥区非常小,只有一条指令,而 Lock 的互斥区是拿锁到放锁之间的区域,至少三条指令。

    展开全文
  • i++ 是线程安全的吗

    千次阅读 2018-08-28 03:06:43
    由于线程共享栈区,不共享堆区和全局区,所以当且仅当 i 位于栈上安全,反之不安全(++i也同理). 因为如果全局变量话,同一进程中不同线程都有可能访问到。对于读值,+1,写值这三步操作,在这...

    i++不是原子操作,也就是说,它不是单独一条指令,而是3条指令(3条汇编指令)

    1、从内存中把i的值取出来放到CPU的寄存器中

    2、CPU寄存器的值+1

    3、把CPU寄存器的值写回内存

    由于线程共享栈区,不共享堆区和全局区,所以当且仅当 i 位于栈上是安全的,反之不安全(++i也同理).   因为如果是全局变量的话,同一进程中的不同线程都有可能访问到。对于读值,+1,写值这三步操作,在这三步任何之间都可能会有CPU调度产生,造成i的值被修改,造成脏读脏写。

    volatile不能解决这个线程安全问题。因为volatile只能保证可见性,不能保证原子性。

    展开全文
  • 线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程其他的线程共享进程所拥有全部资源. 一个线程可以创建和撤销另一个线程; 同一个进程中...
  • 这样多线程的时候一个线程修改了一个变量,另一个线程可能不知道的,所以我认为多线程共享的全局变量应该使用volatile声明才安全。 但是在实际开发当中,很少看到多线程全局共享变量加volatile。不知道这样可以...
  • 进程中各个线程共享代码、数据和文件等资源,记录线程运行状态空间(TCB)每个线程单独有一个每个进程都需要它自己私有线程控制块(TCB)列表,用来跟踪记录它各个线程状态信息(PC、栈指针、寄存器)线程实现...
  • 线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程其他的线程共享进程所拥有全部资源。进程与线程区别进程资源分配最小单位...
  • 线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程其他的线程共享进程所拥有全部资源。 进程与线程区别 进...
  • 本文将介绍用多线程解决数据传输效率的问题,在很多大数据场景中要求我们对于数据采集的时候...线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针、程序计数器等),但代码区是共享的,即不同的线程...
  • 一、线程安全线程安全指在多线程...不同的线程只有其私有栈空间与其寄存器的数据私有的,其余都是共享的!因此临界资源指,全局变量、静态变量、堆空间等.如果我们能够确保互斥地访问临界资源,那么整个线程就会处...
  • 答:不能。虽然volatile提供了同步的机制,但是...当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。volatile变量不会被缓存在寄存器或...
  • 进程和线程的区别进程资源分配最小单位,线程是CPU调度最小单位所有与进程相关资源,都被记录在PCB中进程抢占处理机调度单位,线程属于某个进程,共享其资源![]线程只由堆栈寄存器、程序计数器和TCB...
  • CPU核数跟多线程的关系

    千次阅读 2018-09-29 14:30:03
    一直以来有这样疑惑,单核CPU...a)进程之间相互独立,不共享内存和数据,线程之间内存和数据公用,每个线程只有自己一组CPU指令、寄存器和堆栈,对于线程来说只有CPU里东西自己独享,程序中其...
  • 一直以来有这样疑惑,单核CPU...a)进程之间相互独立,不共享内存和数据,线程之间内存和数据公用,每个线程只有自己一组CPU指令、寄存器和堆栈,对于线程来说只有CPU里东西自己独享,程序中
  • 线程安全问题 -- 转载

    2021-03-11 20:30:24
    线程安全问题i++为什么不是线程安全...由于线程共享栈区,不共享堆区和全局区,所以当且仅当 i 位于栈上安全,反之不安全(++i 也同理). 因为如果全局变量话,同一进程中不同线程都有可能访问到。对于读值,+1
  • Java内存模型在 JDK1.2 之前,Java内存模型实现总是从主存(即共享内存)读取变量,不需要进行特别注意。而在当前 Java 内存模型下,线程可以把变量保存本地内存(比如机器的寄存器)中,而不是直接在主存中...
  • 进程&线程(持续更)

    2015-10-10 12:08:33
    进程的所有信息对该进程的所有线程是共享的,包括可执行的程序文本、程序的全局内存和堆内存、栈以及文件描述符; 线程包含了表示进程内执行环境必需的信息,其中包括进程中标识线程线程ID、一组寄存器值、栈、
  • 操作系统--线程

    2014-04-22 22:59:00
    线程cup使用基本单元,它和同属一个进程其他线程共享一定资源(代码段,数据段等) 线程包含线程ID 程序计数器 寄存器 和栈 这一点类似于进程 为什么要设立线程?只用进程不行吗?实际上,进程创建很消耗资源,...
  • CPU核心数目 与 多线程

    万次阅读 2017-03-16 22:05:23
    CPU核数跟多线程的关系一直以来有这样...a)进程之间相互独立,不共享内存和数据,线程之间内存和数据公用,每个线程只有自己一组CPU指令、寄存器和堆栈,对于线程来说只有CPU里东西自己独享,程序中
  • 线程共享资源 1.文件描述符表 2.每种信号处理方式 3.当前工作目录 4.用户ID和组ID 5.内存地址空间 (.text/.data/.bss/heap/共享库) 线程非共享资源 1.线程id 2.处理器现场和栈指针(内核栈) 3.独立栈空间...
  • Win32多线程程序设计--源代码

    热门讨论 2012-04-22 17:09:08
     Threads(线程比processes(进程)更小执行单元,CPU调度与时间分配皆以threads为对象。  计算机领域中早就存在threads观念和技术,但是早期个人电脑操作系统(主要DOS),别说multithread,连...
  • Win32多线程程序设计--详细书签版

    热门讨论 2012-04-22 16:59:13
     Threads(线程比processes(进程)更小执行单元,CPU调度与时间分配皆以threads为对象。  计算机领域中早就存在threads观念和技术,但是早期个人电脑操作系统(主要DOS),别说multithread,连...

空空如也

空空如也

1 2 3
收藏数 45
精华内容 18
关键字:

寄存器是线程共享的吗