精华内容
下载资源
问答
  • 多线程读取同一变量
    千次阅读
    2019-02-22 11:40:56

    一、临界区
    临界区又称关键代码段,指的是一小段代码在代码执行前,他需要独占一些资源。程序中通常将多线程同时访问的某个资源作为临界区,需要定义一个CRITICAL_SECTION类型的变量,然后调用InitializeCriticalSection函数对变量进行初始化;

    函数声明:

    VOID InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection );
    lpCriticalSection:一个CRITICAL_SECTION结构指针,表示用于初始化的临界区;

    InitializeCriticalSection函数在内部设置了CRITICAL_SECTION结构的某些成员变量,所以他不会失败。

    为了将某一段代码定义为临界区,需要调用EnterCriticalSection函数;

    VOID WINAPI EnterCriticalSection(__inout LPCRITICAL_SECTION lpCriticalSection);
    该函数的作用是判断是否有线程访问临界区资源,如果没有,就改变CRITICAL_SECTION结构的成员变量的值,赋予当前线程访问权,函数立即返回;如果有线程正在访问资源,则进入等待状态,直到没有线程访问。

    释放资源函数:

    void WINAPI LeaveCriticalSection( _Inout_LPCRITICAL_SECTION lpCriticalSection);
    释放CRITICAL_SECTION结构指针

    void WINAPI DeleteCriticalSection(_Inout_ LPCRITICAL_SECTION lpCriticalSection);
    二、学习案例
    用三个线程同时运行将一个变量增加到30;

    临界区对象类

    #ifndef CAUTO_LOCK_H__
    #define CAUTO_LOCK_H__
     
    class CAutoLock
    {
    public:
        CAutoLock();
        ~CAutoLock();
     
        void Lock();
        void UnLock();
     
    private:
        CRITICAL_SECTION m_Section;
    };
     
    #endif
    #include "stdafx.h"
    #include "CAutoLock.h"
     
    CAutoLock::CAutoLock()
    {
        InitializeCriticalSection(&m_Section);
        //Lock();如果是用的时候只定义锁对象,可以不手动进入临界区和退出临界区
    }
     
    CAutoLock::~CAutoLock()
    {
        DeleteCriticalSection(&m_Section);
        //UnLock();
    }
     
    void CAutoLock::Lock()
    {
        EnterCriticalSection(&m_Section);
    }
     
    void CAutoLock::UnLock()
    {
        LeaveCriticalSection(&m_Section);
    }
    三个线程创建类

    #ifndef _TEST_CRITICAL_SECTION_H__
    #define _TEST_CRITICAL_SECTION_H__
    #include "CAutoLock.h"
    class TestCriticalSection
    {
    public:
        TestCriticalSection();
        ~TestCriticalSection();
     
        void StartThread();//开始线程函数
     
        
        static DWORD __stdcall ThreadFun1(LPVOID lParam);//线程回调函数1
        static DWORD __stdcall ThreadFun2(LPVOID lParam);//线程回调函数2
        static DWORD __stdcall ThreadFun3(LPVOID lParam);//线程回调函数3
     
    private:
        HANDLE m_hThread1;
            HANDLE m_hThread2;
        HANDLE m_hThread3;
     
        CAutoLock m_lock;//三个线程公用的临界区锁
     
        static int m_nTotals;
    };
     
    #endif
    #include "stdafx.h"
    #include "CCriticalSection.h"
    #include <iostream>
    using namespace std;
     
    int TestCriticalSection::m_nTotals = 0;//初始化静态成员变量
     
    TestCriticalSection::TestCriticalSection()
    {
        m_nTotals = 0;
            m_hThread1 = INVALID_HANDLE_VALUE;
        m_hThread2 = INVALID_HANDLE_VALUE;
    }
     
    TestCriticalSection::~TestCriticalSection()
    {
        if (m_hThread1 != NULL)
        {
            CloseHandle(m_hThread1);
            m_hThread1 = NULL;
        }
     
        if (m_hThread2 != NULL)
        {
            CloseHandle(m_hThread2);
            m_hThread2 = NULL;
        }
     
        if (m_hThread3 != NULL)
        {
            CloseHandle(m_hThread3);
            m_hThread3 = NULL;
        }
    }
     
    DWORD __stdcall TestCriticalSection::ThreadFun1(LPVOID lParam) //static只需要加在类定义里,类定义外面的函数定义前不能写static
    {
        DWORD dRet = TRUE;
        TestCriticalSection * pThis = static_cast<TestCriticalSection*>(lParam);
        while(1)
        {
            pThis->m_lock.Lock();
            pThis->m_nTotals ++;
            cout<<"ThreadFun1: m_nTotals "<<pThis->m_nTotals<<endl;
            pThis->m_lock.UnLock();
            Sleep(10);
            if (pThis->m_nTotals == 30)
            {
                break;
            }
        }
     
        return dRet;
    }
     
    DWORD __stdcall TestCriticalSection::ThreadFun2(LPVOID lParam)
    {
        DWORD dRet = TRUE;
        TestCriticalSection * pThis = static_cast<TestCriticalSection*>(lParam);
        while(1)
        {
            pThis->m_lock.Lock();
            pThis->m_nTotals ++;
            cout<<"ThreadFun2: m_nTotals "<<pThis->m_nTotals<<endl;
            pThis->m_lock.UnLock();
            Sleep(10);
            if (pThis->m_nTotals == 30)
            {
                break;
            }
        }
        return dRet;
    }
     
    DWORD __stdcall TestCriticalSection::ThreadFun3(LPVOID lParam)
    {
        DWORD dRet = TRUE;
        TestCriticalSection * pThis = static_cast<TestCriticalSection*>(lParam);
        while(1)
        {
            pThis->m_lock.Lock();
            pThis->m_nTotals ++;
            cout<<"ThreadFun3: m_nTotals "<<pThis->m_nTotals<<endl;
            pThis->m_lock.UnLock();
            Sleep(10);
            if (pThis->m_nTotals == 30)
            {
                break;
            }
        }
     
        return dRet;
    }
     
    void TestCriticalSection::StartThread()
    {
        m_hThread1 = CreateThread(NULL, 0, &TestCriticalSection::ThreadFun1, this,  0, NULL);
        m_hThread2 = CreateThread(NULL, 0, &TestCriticalSection::ThreadFun2, this, 0, NULL);
        m_hThread3 = CreateThread(NULL, 0, &TestCriticalSection::ThreadFun3, this, 0, NULL);
    }
    主函数:

    // CriticalSection.cpp : 定义控制台应用程序的入口点。
    //
     
    #include "stdafx.h"
    #include "CCriticalSection.h"
     
    int _tmain(int argc, _TCHAR* argv[])
    {
        TestCriticalSection  CriticalSectionObj;
        CriticalSectionObj.StartThread();
        Sleep(5000);
        system("pause");
        return 0;
    }

    ---------------------

    转载于:https://blog.csdn.net/qwerdf10010/article/details/79657821

    更多相关内容
  • 一、什么时候应该使用多线程?今天看到一个问题,突然有感而发,想聊下这个话题。不知道大家有没有想过这个问题,就是什么时候我该使用多线程呢?使用多线程就一定会提升系统性能吗?1、其实是否应该使用多线程在很...

    一、什么时候应该使用多线程?

    今天看到一个问题,突然有感而发,想聊下这个话题。

    不知道大家有没有想过这个问题,就是什么时候我该使用多线程呢?使用多线程就一定会提升系统性能吗?

    1、其实是否应该使用多线程在很大程度上取决于应用程序的类型。

    • 计算密集型(如纯数学运算) 的, 并受CPU 功能的制约, 则只有多CPU(或者多个内核) 机器能够从更多的线程中受益, 单CPU下, 多线程不会带来任何性能上的提升, 反而有可能由于线程切换等额外开销而导致性能下降
    • IO密集型的,当我的应用必须等待缓慢的资源(如网络连接或者数据库连接上返回数据)时,那么多线程会让系统的CPU充分的利用起来,当一个线程阻塞挂起时,另一个线程可以继续使用CPU资源。
    • 其实,就是多线程不会增加CPU的处理能,而是能够更加充分地利用CPU资源。

    由于同一进程的多个线程是共享同一片内存资源的,在带来方便的同时也必然会增加其复杂性,如何保证多线程访问数据的一致性问题等。而多线程属于编程中容易翻车的地方。并且多线程编程问题的测试定位也是比较难的。总体来说,好的多线程是写出来,将多线程问题寄希望于测试中发现, 无疑是极度不可靠的。SO,努力的学习吧。

    Java API 与多线程息息相关的的几大关键字:volatile、synchronized、 wait、 notify. 理解了这几个关键字,就可以编写多线程的代码了。

    二、什么时候需要加锁?

    在多线程场合下,最重要的就是保障数据的一致性问题,而保障数据一致性问题,就需要借助于锁了。

    其实我们在多线程的场景下应该搞清楚一个问题,就是到底什么需要保护?并不是所有的的数据都需要加锁保护,只有那些涉及到被多线程访问的共享的数据才需要加锁保护。

    锁的本质其实就是确保在同一时刻,只有一个线程在访问共享数据,那么此时该共享数据就能得到有效的保护。

    举例说明下,比如我们想构造一个多线程下安全的单向链表:

    假如现在有两个线程在操作这个链表,一个写线程插入一个新元素7,另一个读线程遍历链表数据,如果不使用任何锁,那就有可能出现下面的执行顺序:

    e71ef1a2adacaff03080304d5513206a.png

    通过上面的例子我们可以明显看到在多线程下操作这个链表,有可能会导致多线程读到的数据不完整,只有从链表头部到元素7的位置的数据。由此可见,不加入任何保护措施的多线程保护,势必会导致数据的混乱。为了避免数据一致性问题,我们就需要将操作该队列的代码放入同步块内(锁的对象也就是这个链表实例),来确保同一时刻只有一个线程可以访问该链表。

    如何加锁?

    这里简单的说下,一般我们都是使用synchronized(如果没有特殊需求建议直接使用这个关键字,jdk新版本它真的很快),记住synchronized 锁的就是对象头。

    简单的说下,主要有下面几种用法:

    • synchronized 放在方法上,锁的是当前synchronized 方法的对象实例
    • synchronized在static 方法上,锁的是synchronized 方法的class 类对象,注意这里class 其实也是一个对象。
    • synchronized(this)在代码块中,锁的是代码块括号内的对象,这里this指的就是调用这个方法的类实例对象

    三、 多线程中易犯的错误

    1、锁范围过大

    共享资源访问完成后, 后续的代码没有放在synchronized同步代码块之外。 会导致当前线程长期无效的占用该锁, 而其它占用该锁的线程只能等待, 最终导致性能受到极大影响。

     public void test() {        synchronized(lock){        ... ... //正在访问共享资源        ... ... //做其它耗时操作,但这些耗时操作与共享资源无关    } }

    面对上面这种写法,会导致此线程长期占有此锁,从而导致其他线程只能等待,下面来讨论下解决方法:

    1)单CPU场景下,将不需要同步的耗时操作拿到同步块外面,有的情况可以提升性能,有的却不行。

    • CPU密集型的代码 ,不存在磁盘IO/网络IO等低CPU消耗的代码。 这种情况下, CPU 99%都在执行代码。 因此缩小同步块也不会带来任何性能上的提升, 同时缩小同步块也不会带来性能上的下降。
    • IO密集型的代码,在执行不消耗CPU的代码时,其实CPU属于空闲状态的。如果此时让CPU工作起来就可以带来整体上性能的提升。所以在这种情况下,就可以将不需要同步的耗时操作移到同步块外面了。

    2)多CPU场景下,将耗时的CPU操作拿到同步块外面,总是可以提升性能的

    • CPU密集型的代码,不存在IO操作等不消耗CPU的代码片段。因为当前是多CPU,其他CPU也可能是空闲的。所以在缩小同步块的时候,也会让其他线程尽快的执行这段代码从而带来性能上的提升。
    • IO密集型的代码,因为当前PCU都是空闲的状态,所以将耗时的操作放在同步块外面,一定会带来整体上的性能提升。

    当然,不管怎么样,缩小锁的同步范围对于系统来说都是百利而无一害的,因此上面的代码应该改为:

     public void test() {        synchronized(lock){        ... ... //正在访问共享资源    }        ... ... //做其它耗时操作,但这些耗时操作与共享资源无关 }

    综上所述,一个重点,就是只将访问共享资源的代码放在同步块内,保证快进快出。

    2、死锁的问题

    死锁要知道的:

    • 死锁,简单地说就是两个线程或多个线程在同时等待被对方持有的锁导致的,死锁会导致线程无法继续执行并被永久挂起。
    • 如果线程发生了死锁,那我们就能从线程堆栈中明显的看到”Found one Java-level deadlock“,并且线程栈还会给出死锁的分析结果。
    • 死锁这种问题如果发生在关键系统上就可能会导致系统瘫痪,如果想要快速恢复系统,临时唯一的方法就是保留线程栈先重启,然后再尽快的恢复。
    • 死锁这种问题有时候测试是很难被立即发现的,很多时候在测试时能否及时发现这类问题,就全看你的运气和你准备的测试用例了。
    • 避免死锁这类问题,唯一的办法就是改代码。但一个可靠的系统是设计出来的,而不是通过改BUG改出来的,当出现这种问题的时候就需要从系统设计角度去分析了。
    • 有人会认为死锁会导致CPU 100%,其实对也不对。 要看使用的什么类型的锁了,比如synchronized导致的死锁,那就不会导致CPU100%,只会挂起线程。但如果是自旋锁这种才可能会消耗CPU。

    3、共用一把锁的问题

    就是多个共享变量会共用一把锁,特别是在方法级别上使用synchronized,从而人为导致的锁竞争。

    上例子,下面是新手容易犯的错误:

    1 public class MyTest2 {3 Object shared;4 synchronized void fun1() {...} //访问共享变量shared5 synchronized void fun2() {...} //访问共享变量shared6 synchronized void fun3() {...} //不访问共享变量shared7 synchronized void fun4() {...} //不访问共享变量shared8 synchronized void fun5() {...} //不访问共享变量shared9 }

    上面的代码每一个方法都被加了synchronized ,明显违背了保护什么锁什么的原则。

    三、线程数我们一般设多少比较合理呢?

    其实大家都知道,在大多数场合下多线程都是可以提高系统的性能和吞吐量,但一个系统到底多少个线程才是合理的?

    总的来说,线程数量太多太少其实都不太好,多了会因为线程频繁切换导致开销增大,有时候反而降低了系统性能。少了又会导致CPU资源不能充分的利用起来,性能没有达到瓶颈。

    所以,系统到底使用多少线程合适,是要看系统的线程是否能充分的利用了CPU。其实实际情况,是很多时候不消耗CPU,如:磁盘IO、网络IO等。

    磁盘IO、网络IO相比CPU的速度,那简直是相当的慢的,在执行IO的这段时间里CPU其实是空闲的。如果这时其他线程能把这空闲的CPU利用上,就可以达到提示系统性能和吞吐的目的。

    其实上面我们也提到过,也就是两种计算特性:

    CPU密集型: 因为每个CPU都是高计算负载的情况,如果设置过多的线程反而会产生不必要的上下文切换。所以,一般线程我们会设置 CPU 核数 + 1就可以了,为啥要加1 呢,即使当计算(CPU)密集型的线程偶尔由于页缺失故障或者其他原因而暂停时,这个“额外”的线程也能确保 CPU 的时钟周期不会被浪费,其实就是个备份。

    IO密集型:因为大量的IO操作,会导致CPU处于空闲状态,所以这时我们可以多设置些线程。 所以, 线程数 = CPU 核心数 * (1+ IO 耗时/CPU 耗时) 就可以了,希望能给你点启发。

    展开全文
  • C#多线程互斥实例 多线程获取同一变量(不重复)。是一个很好的学习例子
  • 1.Qt下,多线程使用互斥锁安全访问同一全局变量;2.源码中定义了ThreadA和ThreadB,定义变量后,依次调用函数start()来启动重写的run()函数
  • } } 问题:多线程调用access()方法时,接口访问次数统计的结果是否能保证准确呢? 自定义线程中的实例变量针对其他线程有共享和不共享之分。 1.1、多个线程共享同一个变量,产生线程安全问题。 public class ...

    目录

    一、经典面试题

    二、内存模型概念

    2.1、CPU内存模型

    2.2、JVM内存模型

    三、基于内存模型一步一步分析缓存一致性问题

    四、解决并发问题的方法有哪些 

    4.1、局部变量

    4.2、不可变对象

    4.3、ThreadLocal

    4.4、CAS原子类

    4.5、Synchronized/ReentrantLock加锁


    一、经典面试题

    有没有在JAVA笔试或面试中遇见过这样的题目:统计服务器某个接口的访问次数。

    public class AccountCount {
    
        int accessCount;
    
        public void access(){
            accessCount ++;
        }
    }

    问题:多线程调用access()方法时,接口访问次数统计的结果是否能保证准确呢?

    自定义线程中的实例变量针对其他线程有共享和不共享之分。

    在这里插入图片描述

    1.1、多个线程共享同一个变量,产生线程安全问题。

    public class AccountCount extends Thread{
        int accessCount;
    
        public void access(){
            accessCount ++;
            System.out.println(Thread.currentThread().getName() + accessCount);
        }
    
        @Override
        public void run() {
            access();
        }
    
        public int getAccount() {
            return accessCount;
        }
    
        public static void main(String[] args) {
    
            AccountCount accountCount = new AccountCount();
            new Thread(accountCount,"A").start();
            new Thread(accountCount,"B").start();
            new Thread(accountCount,"C").start();
            new Thread(accountCount,"D").start();
            new Thread(accountCount,"E").start();
            new Thread(accountCount,"F").start();
        }
    
    }
    B2
    E5
    F6
    A6
    D6
    C4
    

    2)、不共享数据时每个线程都拥有自己作用域的变量,且多个线程之间相同变量名的值也不相同

    public class AccountCount extends Thread{
        int accessCount;
    
        public void access(){
            accessCount ++;
            System.out.println(Thread.currentThread().getName() + accessCount);
        }
    
        public AccountCount(String accessCount) {
            this.setName(accessCount);
        }
    
        @Override
        public void run() {
            access();
        }
    
        public int getAccount() {
            return accessCount;
        }
    
        public static void main(String[] args) throws InterruptedException {
            new AccountCount("A").start();
            new AccountCount("B").start();
            new AccountCount("C").start();
            new AccountCount("D").start();
            new AccountCount("E").start();
            new AccountCount("F").start();
        }
    }
    A1
    B1
    E1
    D1
    F1
    C1

    二、内存模型概念

            我们的程序是运行在Java虚拟机上面的,Java虚拟机本身有自己的内存模型, Java的内存模型和计算机的CPU内存模型又有很多相同之处。

    2.1、CPU内存模型

            计算机在执行程序的时候,每条指令都是在CPU中执行的,而在CPU执行指令的过程中会涉及到数据的读取和写入操作,而在计算机运行过程中所有的数据都是存放在主存中的(比如一台普通的4C8G机器,这个8G就是指主存的容量),CPU则是从主存中读取数据进行运行。

            由于CPU执行速度非常快,比计算机主存的读取和写入的速度快了很多,这样就会导致CPU的执行速度大大下降。

            因此,每个CPU都会自带一个高速缓冲区,在运行的时候,会将需要运行的数据从计算机主存先复制到CPU的高速缓冲区中,然后CPU再基于高速缓冲区的数据进行运算,运算结束之后,再将高速缓冲区的数据刷新到主存中。这样CPU的执行指令的速度就可以大大提升。

    2.2、JVM内存模型

            JVM启动之后,操作系统会为JVM进程分配一定的内存空间,这部分内存空间就称为“主内存”。另外Java程序的所有工作都由线程来完成,而每个线程都会有一小块内存,称为“工作内存”即线程栈。 Java中的线程在执行的过程中,会先将数据从主内存(指堆内存)中复制到线程的工作内存,然后再执行计算,执行计算之后,再把计算结果刷新到“主内存”中。

    三、基于内存模型一步一步分析缓存一致性问题

            假设现在有两个线程同时访问了这个接口的access()方法,两个线程都执行了accessCount++,在内存中是怎么样执行的呢?

    首先我们要明白,计算机需要执行accessCount++ 这个语句,需要分为以下3个步骤:

    1. 从主存中读取accessCount的值

    2. 将accessCount的值进行加1

    3. 将accessCount的值写回主存中

    先来看看第1步,假设两个线程同时来执行accessCount()方法

    上面图中在第1个步骤的时候,线程1和线程2都会把accessCount的值从主存中复制到线程所属的工作内存中,两个线程此时得到的accessCount的值都是0。

    接着两个线程执行第2步操作:将accessCount的值进行加1:

    5.jpg

    图中,线程1和线程2都进行了第2步的计算,然后线程1得到的结果是 accessCount=1,线程2得到的结果也是accessCount=1。

    接着两个线程都到了第三步:将accessCount的值写回主存中:

    6.jpg

    线程1和线程2都计算完之后就会将计算结果刷新回主存,特别注意一下图中红框的内容,这是两个线程把计算结果刷新回主存的步骤,两个红框中操作的执行顺序不分先后(在实际运行情况,两个操作的顺序是随机的,可能是线程1先刷新,也可能是线程2先刷新),但是这不影响结果,因为无论是线程1还是线程2,写回主存的结果都是accessCount=1。

    但是实际上,我们观察到是2个线程都执行了一次access()方法,按照预期来说accessCount的值应该是等于2才对。

    这种多个线程访问同一个对象时,调用这个对象的方法得到不正确的结果,这种问题称为线程安全问题。

    四、解决并发问题的方法有哪些 

    解决并发问题方法分为两类:无锁和有锁。

    无锁分为:局部变量、不可变对象、ThreadLocal、CAS原子类。

    有锁分为:synchronized关键字 和 reentranLock可重入锁。

    4.1、局部变量

    善用局部变量可以避免出现线程安全问题,因为局部变量仅仅存在于每个线程的工作内存中。

    public void test(){
        int i = 0;
        i++;
        System.out.println(i);
    }

    只有当每个线程都执行到int i =0时,会在各自线程栈中创建该变量。

    4.2、不可变对象

    不可变对象是指一经创建,则对外的状态就不会改变的对象。如果一个对象的状态不变,无落多少个线程,对其如何操作,都不改变。例如字符串对象就是不可变对象。String s = "a",指字面值a是不可变的,而引用s可以变。

    4.3、ThreadLocal

    ThreadLocal本质是在每个线程有自己的一个副本,每个线程的副本互不影响。

     一个命令为“I”的ThreadLocal类,他会在每个线程都有一个Integer对象,虽然每个线程都会在主内存把Integer对象拷贝到工作内存,但是拷贝的不是一个对象。

    4.4、CAS原子类

    CAS(Compare And Swap)比较交换。

    对CAS的理解,CAS是一种无锁算法,CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。

    CAS比较与交换的伪代码可以表示为:

    do{
    
    备份旧数据;
    
    基于旧数据构造新数据;
    
    }while(!CAS( 内存地址,备份的旧数据,新数据 ))

     其中内存值V是56,旧的预期值CPU1和CPU2中的值,要修改的新值为最新值。

            在Java中,以Atomic为前缀的一系列类都采用CAS思想。

    Atomic使用的是无锁的CAS操作,基于乐观锁,并发性能比较高,可以多个线程同时执行,保证线程安全。

    Atomic简单使用:首先声明一个AtomicInteger的成员变量,然后再atomicAdd()方法中调用incrementAndGet()。

    private AtomicInteger counter = new AtomicInteger(0);
    
        public void actomicAdd(){
            counter.incrementAndGet();
        }

    atomicInterger源码分析:内部有一个Unsafe实例,Unsafe类提供硬件级别的原子操作,因为Java无法直接访问到操作系统底层的硬件,故Java使用native方法进行扩展,其中Unsafe类就是一个操作入口。Unsafe提供几种功能:CAS操作、内存的分配和释放、挂起和恢复线程、定位对象字段内存地址、修改对象的字段值等。

    其中对于Unsafe类的CAS的操作,主要调用了unsafe的getAndInt()方法:

     首先通过var5获取旧值,然后调用compareAndSwapInt()通过CAS操作对数据比较交换,如果操作失败进行while循环直到成功。

    4.5、Synchronized/ReentrantLock加锁

    Synchronized和ReentrantLock都采用悲观锁策略。

    Synchronized是语言层面

    ReentrantLock是编程方式实现

    public class ReentratLockTest {
        
        private int count = 0;
        private ReentrantLock reentrantLock = new ReentrantLock();
        
        public void lockMethod(){
            reentrantLock.lock();
            try {
                add();
            } finally {
                reentrantLock.unlock();
            }
        }
        
        public synchronized void lockMethod2(){
            add();
        }
        
        private void add(){
            count++;
        }
    }

    加锁原理:首先两个线程争抢同一把锁,假如线程1获取到锁,而线程二没获取锁就会进入等待队列,等到线程1执行完代码逻辑,会去通知线程2,此时线程2重新尝试获取锁,假如线程2获取成功,则执行代码。

    展开全文
  • 多线程访问同一变量是否需要加锁

    千次阅读 2015-09-08 21:49:06
    对于多线程访问同一变量是否需要加锁的问题,先前大家都讨论过。今天用代码验证了一下之前的猜想:32位CPU与内存的最小交换数据为4字节/次,这也是结构体要对齐4字节的原因。在物理上,CPU对于同一4字节的内存单元,...
    对于多线程访问同一变量是否需要加锁的问题,先前大家都讨论过。今天用代码验证了一下之前的猜想:32位CPU与内存的最小交换数据为4字节/次,这也是结构体要对齐4字节的原因。在物理上,CPU对于同一4字节的内存单元,不可能写2个字节的同时,又读了3字节。

    测试环境为:

    XEON 2CPU*2
    Windows7

    采用50,50,50线程交叉读写,试验代码如下:
    C/C++ code
    ?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    int  g_test;
    int  temp;
    BOOL  g_bRunning;
    DWORD  WINAPI thWriteProc1( LPVOID  lParam)
    {
         while (g_bRunning)
         {
             g_test = 12345678;
             Sleep(1);
         }
         return  0;
    }
    DWORD  WINAPI thWriteProc2( LPVOID  lParam)
    {
         while (g_bRunning)
         {
             g_test = 13579246;
             Sleep(1);
         }
         return  0;
    }
     
    DWORD  WINAPI thReadProc( LPVOID  lParam)
    {
         while (g_bRunning)
         {
             temp = g_test; //读取值
             if  ( temp != 12345678 && temp != 13579246 )
             {
                 g_bRunning = FALSE;
                 CString str;
                 str.Format( "read error!%d" , temp);
                 AfxMessageBox(str);
                 break ;
             }
             Sleep(1);
         }
         return  0;
    }
    void  CTestMultiyAccessIntDlg::OnButton1() 
    {
         g_bRunning = TRUE;
         for  int  i = 0; i < 50; i++ )
         {
             //创建50个写线程1
             CreateThread( NULL, 0, thWriteProc1, NULL, 0, NULL );
         }
         for  int  i = 0; i < 50; i++ )
         {
             //创建50个写线程2
             CreateThread( NULL, 0, thWriteProc2, NULL, 0, NULL );
         }
         for  int  i = 0; i < 50; i++ )
         {
             //创建50个读线程
             CreateThread( NULL, 0, thReadProc, NULL, 0, NULL );
         }
    }


    测试方法:
    改变g_test的类型,给g_test赋予不同的值(不要超过类型的上限值)

    测试现象:
    当g_test为int,short char时,不存在多线程交叉读写错误的问题
    当g_test为double, float, __int64时,存在多线程交叉读写错误的问题,对于__int64,当赋值小于0xFFFFFFFF时不出错,当大于0xFFFFFFFF时出错
    当g_test为CString时,存在交叉读写错误,有时候程序崩溃
    另:不加Sleep(1)机器卡死过,CPU占用率达到100%,4个核心占用率全满,可以保证运行在多核环境下

    现象分析:
    (1)int short char均为小于4字节的连续内存块,CPU一条指令就可以读写它们的值,CPU不可能同一个时间执行两条指令
    (2)double为8字节,如果写线程先写了4字节,读线程读了8字节,这自然导致数据被破坏
    (3)float也为4字节,我也不是太清楚为什么不行,可能是VC对浮点数的处理比较特殊有关,浮点数具有复杂内存结构
    (4)__int64为8字节,存在和(2)相同的情况,如果__int64小于等于0xFFFFFFFF,相当于只改变了低4字节,因此就没有问题
    (5)CString为类类型,具有复杂结构,显然不行

    结论:
    1.对于int,short,char,BOOL等小于等于4字节的简单数据类型,如果无逻辑上的先后关系,多线程读写可以完全不用加锁
    2.尽管float为4字节,多线程访问时也需要加锁
    3.对于大于4字节的简单类型,比如double,__int64等,多线程读写必须加锁。
    4.对于所有复杂类型,比如类,结构体,容器等类型必须加锁

    尽管对int等类型的多线程读写不需要加锁,但是逻辑上有必要加锁的还是应该加锁
    例如:对于一个多线程访问的全局变量int g_test
    int count = g_test/1024;
    int mod = g_test%1024;
    由于是两条语句,执行完第一条之后,别的线程很可能已经修改了g_test的值,如果希望这两条语句执行时,g_test不发生变化,就必须加锁,以保证两条语句执行的整体性。
    Lock();
    int count = g_test/1024;
    int mod= g_test%1024;
    UnLock();
    如果不加锁,也可以改为先保存到一个临时变量里
    int temp = g_test;
    int count = temp/1024;
    int mod = temp%1024;
    展开全文
  • 文章目录示例CPU的内存模型Java...那么问题来了:多线程调用access()方法时,接口访问次数统计的结果是否能保证准确呢? 显而易见:不能。 CPU的内存模型 接下来分析一下为什么上面统计的结果会有问题. 我们先来简单理
  • 今天小编就为大家分享一篇python进阶之多线程对同一个全局变量的处理方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧 通常情况下: from threading import Thread global_num = 0 def ...
  • 对于多线程访问同一变量是否需要加锁的问题,先前大家都讨论过。今天用代码验证了一下之前的猜想:32位CPU与内存的最小交换数据为4字节/次,这也是结构体要对齐4字节的原因。在物理上,CPU对于同一4字节的内存单元,...
  • #由于多线程不像多进程一样,每一个进程都一个独立的资源块,线程之间是共享主线程的一个资源块(虽然这样说不合适) #这样虽然方便了线程之间的数据传递,但是又会由于线程之间执行顺序的不确定,导致最后的结果...
  • 浅析多线程访问同一资源的问题

    千次阅读 2021-11-30 17:09:29
    线程在对同一个资源进行访问时要上锁 synchronized加在静态方法上和在代码中sychronized这个类是等价的: 加在非静态方法上和在代码中sychronized(this)是等价的 synchronized static void m() == sychronized ...
  • 多线程操作同一变量无参数版 非GIL import threading from time import sleep from datetime import datetime date_time_format = '%y-%m-%d %H:%M:%S' def date_time_str(date_time): return datetime....
  • java线程循环监听类变量值的变化需求代码设计代码实现问题分析 需求 一个仓库只能存放10个商品,库存数量始终要保持在10,如果卖出去几个,那么立马又从商品供应商那里买回来,保证仓库存满10个商品。 代码设计 做一...
  • C++多线程同时读写变量

    千次阅读 2021-11-12 17:01:34
    线程同时写一变量会否造成问题【c++吧】_百度贴吧 Saingel :不一定,要看哪种了,stdstring那种随写入操作而内存位置或大小变更的对象肯定会有问题,如果是在固定的内存上写,不写出界基本没问题,最多运算...
  • } else { this.textBox1.Invoke(new Action(() => { this.textBox1.AppendText("我是线程" + i.ToString() + ">>>" + "剩余票数" + num.ToString() + Environment.NewLine); })); Thread.Sleep(10); } } } } Thread...
  • 在不添加互斥锁的情况下,分别在两个线程对a变量进行累加。 1.计算量不是很大的话,最终的计算结果看似是满足预期结果的。 虽然代码顺序是先执行xianCheng_1的,但最终结果的输出顺序有可能是xianCheng_1先输出,也...
  • 多线程知识简介 同一进程中可以包含多个线程,由于进程中的多个线程可以共享进程中的资源,所以使同一进程中的多个线程之间通信相对比较简单。 当需要有多个线程来访问一个全局变量时,通常我们会在这个全局变量前...
  • 最近接触到多线程读取摄像头数据的问题,需求是读取同步,期望通过回调的方式实现,找到两个最为接近的教程: 1.多线程读取IP摄像头(Python版) 2.Python 获取多线程返回值的两种方式
  • C++多线程同时读同一文件

    千次阅读 2020-04-06 11:25:32
    C++多线程同时读同一文件 #include <thread> #include <iostream> #include <fstream> #include <string> #include <sstream> #include <vector> #include <chrono> ...
  • Qt 多线程同步之读写锁

    千次阅读 2021-03-15 22:11:35
    如果在一个程序中有线程读取某个变量,使用互斥量时也必须排队。而实际上若只是读取一个变量,是可以让个线程同时访问的,这样互斥量就会降低程序的性能。 lockForRead():以只读方式锁定资源,如果有其他...
  • Linux C中多线程与volatile变量volatile 修饰的变量表示改变量的值是易变的,编译器不对其进行优化,访问该变量的时候不会从寄存器读取, 而是直接从内存读取变量。在多线程环境下,每个线程都有一个独立的寄存器,...
  • 今天小编就为大家分享一篇python进阶之多线程对同一个全局变量的处理方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧 通常情况下: from threading import Thread global_num = 0 def ...
  • 多线程操作同一个变量

    万次阅读 2019-03-13 15:52:37
    在java线程并发处理中,有一个关键字volatile的使用目前存在很大的混淆,以为使用这个关键字,在进行多线程并发处理的时候就可以万事大吉。 Java语言是支持多线程的,为了解决线程并发的问题,在语言内部引入了 ...
  • 多线程是否可以同时读取同一内存变量多线程是否可以同时读取同一内存变量?只是对他进行读取不进行写入修改 那么在多线程中是否会因此而出错? itljl 2012-12-23 00:14 读可以,修改不可以。 ...
  • 建立线程加快速度,但是如GloBestResult=1, 线程a和b计算的result分别为2和3匀大于GloBestResult,而b线先改动GloBestReult 然后轮到线程a,那3这个结果就被覆盖了。 怎么解决,加入信号量吗?会不会杀鸡用宰...
  • 查看本章节查看作业目录需求说明:多线程操作同一个实例变量的操作会引发多线程并发的安全问题。现有 3 个线程代表 3 只猴子,对类中的一个整型变量 count(代表花的总数,共 20 朵花)进行操作。该变量代表花的总数,...
  • 多线程编程中,如果多个线程都需要对同一个全局变量进行赋值或者读取操作,那么在每次赋值或读取时,为了确保线程安全,都必须要进行加锁和解锁的操作,这样会在编程时带来很多麻烦。特别是线程多、全局变量多的...
  • Python多线程下的全局变量、局部变量 最近开发一个新功能遇到一个问题,就是在一个A.py文件生成2个线程,调用B.py里的函数,B.py里面有若干global定义全局变量,导致程序得不到想要的结果。B.py文件里面的全局变量,...
  • 线程安全问题分析

    多人点赞 热门讨论 2022-04-14 15:00:37
    文章目录一、抢占式执行二、两个线程修改同一变量三、原子性四、内存可见性五、指令重排序 多线程可以实现并发编程,从而提升效率。但由于多线程的调度的随机性,导致程序出现错误,这就叫做“线程不安全”。如果在...
  • 目录 主内存和工作内存 为什么要分为主内存和工作内存 Java 内存模型和运行时数据区的区别 Java 内存模型中变量的定义 ...不同线程之间无法直接访问对方工作内存中的变量线程变量值的传递均需要在主内存来

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 117,517
精华内容 47,006
热门标签
关键字:

多线程读取同一变量