精华内容
下载资源
问答
  • 多线程访问变量是否要加锁

    千次阅读 2017-01-09 21:35:33
    这个变量即使线程访问冲突,也没有什么大的问题,很纠结是否加锁。 询问同事,arm访问int是原子型的,可以不加锁。 网上查询了下, 1.int变量读写是否是原子操作,是平台相关的,可以看内核相应平台的atomic.h中...

    是否要加锁?

    加锁的代价有多大?

    现在有个变量 static int g_delRecordFlag = 0;多个线程同时访问,加了互斥锁,感觉资源消耗的有点多。

    这个变量即使多个线程访问冲突,也没有什么大的问题,很纠结是否加锁。
    询问同事,arm访问int是原子型的,可以不加锁。
    网上查询了下,
    1.int变量读写是否是原子操作,是平台相关的,可以看内核相应平台的atomic.h中对atomic_set()和atomic_read()的定义,如果仅是一条汇编指令或者一个“=”赋值语句,那么对int型读写就是原子的;
    2.2个线程访问某个全局变量,如果读写int型变量是原子操作,就可以不用保护,一读一写(写操作仅限于赋值,如果自加自减等就可能不是原子操作了),最好用volatile声明,防止被编译器优化;

    3.如果多线程读写数据,而读数据的频率又远大于写数据的频率,使用读写锁保护比较好。由于读锁是共享的,相比互斥锁,程序并发性会好很多。

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

    同时网上也有人说C语言的原子操作并不是很安全的,如果编译器加了优化,那么还是有风险的。

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

    这里我没有加锁,后续进一步研究

    展开全文
  • 一个线程只读并没有改变变量的值并不会有两个线程同时写一个变量产生竞态,所以不用加锁,但是工作中长者给我指导都是多线程必须加锁,所以我也没有深究这个问题,从来没有想过为什么。  过了一段时间后,了解到...

      单线程读单线程写一个变量是否需要加锁,刚毕业的时候我会有这样的想法:一个线程只读并没有改变变量的值并不会有两个线程同时写一个变量产生竞态,所以不用加锁,但是工作中长者给我指导都是多线程必须加锁,所以我也没有深究这个问题,从来没有想过为什么。

      过了一段时间后,了解到原子性这个概念,了解到虽然一个线程读一个线程写,但是因为对一个线程的写和读并非是原子的,读线程可能读到另外一个线程写到一半的值,所以要加锁来保护。但是上面的答案直到我看到下面的文章再一次被推翻了。

      1分析很透彻的C/C++ 基本类型及是否需要多线程锁

      2关于C++多线程程序中简单类型(int/bool)的安全性

      3关于int全区变量读写的原子性

      关于这个观点我也写了个测试发现结果是正确的(因为我测试环境是64位所以char,int,long读写都是原子的)

    
    //  Created by 杜国超 on 19/8/25.
    //  Copyright © 2019年 杜国超. All rights reserved.
    //
    
    #include <memory>
    #include <iostream>
    #include <thread>
    #include <atomic>
    #include <zconf.h>
    #include <vector>
    #include <algorithm>
    
    using namespace std;
    
    char c_v = 0;
    int i_v = 0;
    long long l_v = 0;
    float f_v = 0.0f;
    
    std::atomic_bool flag(true);
    
    void thread_func1() {
        while (flag)
        {
            c_v = '1';
            i_v = 111111111;
            l_v = 111111111;
            f_v = 1111.11111;
            usleep(1);
        }
    }
    
    void thread_func2() {
        while (flag)
        {
            c_v = '2';
            i_v = 222222222;
            l_v = 222222222;
            f_v = 2222.22222;
            usleep(1);
        }
    }
    
    void thread_func3() {
        while (flag)
        {
            char tmpc = c_v;
            if(!(tmpc == '1' || tmpc == '2'))
            {
                printf("char value read error %c\n",tmpc);
            }
    
            int tmpi = i_v;
            if(!(tmpi == 111111111 || tmpi == 222222222))
            {
                printf("int value read error %d\n",tmpi);
            }
    
            long tmpl = l_v;
            if(!(tmpl == 111111111 || tmpl == 222222222))
            {
                printf("long value read error %ld\n",tmpl);
            }
    
            float tmpf = f_v;
            if(!(tmpf == 1111.11111 || tmpf == 2222.22222))
            {
                printf("float value read error %f\n",tmpf);
            }
            usleep(1);
        }
    }
    
    int main() {
        std::vector<thread> threads;
        for(int i = 0 ;i < 50 ; i++)
        {
            threads.push_back(std::move(std::thread(thread_func1)));
        }
        for(int i = 0 ;i < 50 ; i++)
        {
            threads.push_back(std::move(std::thread(thread_func2)));
        }
        std::thread check_thread(thread_func3);
        std::for_each(threads.begin(),threads.end(),[](std::thread& th) {th.join();});
        check_thread.join();
        return 0;
    }

      其实对于上面的问题有一个很好的解释就是:锁本身也是一个基本类型,如果锁的读写不是原子的,那谁来保证锁的线程安全,给锁再加个锁吗。

      这样新的疑问诞生了,既然char,bool,int,读写是原子的,为什么也要加锁。关于这个问题又很多个关键点,首先是仍然是原子性问题比如a++,这个操作并非原子的如果两个线程同时进行这个操作可能会导致操作的丢失,所以如果多线程写没有争论必须要加锁或者使用atomic相关的操作来保证线程的安全性。不过我们这里讨论的是单线程读单线程写是否要加锁,对于非上面提到的基本了类型由于原子性的限制必须保证不能让多线程读到写线程写到一半的值所以必须要加锁。

      保证了原子性后还有一个可见性问题,写线程改变了变量的值读线程不一定能够立马读到改变后的值,但是如果我们可以接受这种延时读取完全可以不做任何额外的处理(比如我们一个值表示当前的池子中的水位,我们写线程加水后把表示水位的值改变为新的状态我们读线程取水,这次取不到可以下次取),当然这个问题也可以通过voliate关键字来解决,最暴力的手段当然还是加锁。在我们可以保证了可见性之后还有一个比较头疼的问题就是cpu执行时的乱序问题,cpu乱序执行分为两个维度。第一个维度的乱序是对于不相干的计算乱序执行。譬如

    MUL R1, R2
    ADD R3, R4

    乘法比加法需要更多的cycle,而这两个指令操作的寄存器没有任何依赖关系,所以可以乱序执行,不必等待MUL执行完以后再执行ADD。第二个维度是memory reordering。具体什么样的规则取决于硬件的内存模型。Intel x64的基础是一种叫做total store ordering(TSO)的模型,大概可以理解为,除了store-load可以reorder以外,其他的都不可以。就是说如果一个store先执行了然后是一个load,他们操作的地址也不同,就可以reorder。这样设计的目的主要是为了掩盖store的latency。具体的实现则是在CPU和缓存之间加一个store buffer(揭开内存屏障的面纱),store发生以后被放到store buffer,从store buffer出来以后才能对其他核可见(visible),来保证,最暴力的手段当然还是加锁。如果你并没有类似的依赖逻辑就是单纯的一个线程读,一个线程写,对于上面提到几一些基本变量你完全可以不做任何处理更不需要加锁。

    展开全文
  • 新博客地址: vonsdite.cn (实际上道理与全局变量线程安全问题相似) ... 为什么多线程读写 shared_ptr 要加锁? 陈硕(giantchen_AT_gmail_DOT_com) 2012-01-28 最新版下载:http://chenshuo.goog...

    新博客地址: vonsdite.cn

    (实际上道理与全局变量线程安全问题相似)

    原文链接: https://blog.csdn.net/solstice/article/details/8547547

    为什么多线程读写 shared_ptr 要加锁?

    陈硕(giantchen_AT_gmail_DOT_com)

    2012-01-28

    最新版下载http://chenshuo.googlecode.com/files/CppEngineering.pdf

    我在《Linux 多线程服务端编程:使用 muduo C++ 网络库》第 1.9 节“再论 shared_ptr 的线程安全”中写道:

    (shared_ptr)的引用计数本身是安全且无锁的,但对象的读写则不是,因为 shared_ptr 有两个数据成员,读写操作不能原子化。根据文档(http://www.boost.org/doc/libs/release/libs/smart_ptr/shared_ptr.htm#ThreadSafety), shared_ptr 的线程安全级别和内建类型、标准库容器、std::string 一样,即:

    • 一个 shared_ptr 对象实体可被多个线程同时读取(文档例1);

    • 两个 shared_ptr 对象实体可以被两个线程同时写入(例2),“析构”算写操作;

    • 如果要从多个线程读写同一个 shared_ptr 对象,那么需要加锁(例3~5)。

    请注意,以上是 shared_ptr 对象本身的线程安全级别,不是它管理的对象的线程安全级别。

    后文(p.18)则介绍如何高效地加锁解锁。本文则具体分析一下为什么“因为 shared_ptr 有两个数据成员,读写操作不能原子化”使得多线程读写同一个 shared_ptr 对象需要加锁。这个在我看来显而易见的结论似乎也有人抱有疑问,那将导致灾难性的后果,值得我写这篇文章。本文以 boost::shared_ptr 为例,与 std::shared_ptr 可能略有区别。

    shared_ptr 的数据结构

    shared_ptr 是引用计数型(reference counting)智能指针,几乎所有的实现都采用在堆(heap)上放个计数值(count)的办法(除此之外理论上还有用循环链表的办法,不过没有实例)。具体来说,shared_ptr<Foo> 包含两个成员,一个是指向 Foo 的指针 ptr,另一个是 ref_count 指针(其类型不一定是原始指针,有可能是 class 类型,但不影响这里的讨论),指向堆上的 ref_count 对象。ref_count 对象有多个成员,具体的数据结构如图 1 所示,其中 deleter 和 allocator 是可选的。

    sp0

    图 1:shared_ptr 的数据结构。

    为了简化并突出重点,后文只画出 use_count 的值:

    sp1

    以上是 shared_ptr<Foo> x(new Foo); 对应的内存数据结构。

    如果再执行 shared_ptr<Foo> y = x; 那么对应的数据结构如下。

    sp2

    但是 y=x 涉及两个成员的复制,这两步拷贝不会同时(原子)发生。

    中间步骤 1,复制 ptr 指针:

    sp3

    中间步骤 2,复制 ref_count 指针,导致引用计数加 1:

    sp4

    步骤1和步骤2的先后顺序跟实现相关(因此步骤 2 里没有画出 y.ptr 的指向),我见过的都是先1后2。

    既然 y=x 有两个步骤,如果没有 mutex 保护,那么在多线程里就有 race condition。

    多线程无保护读写 shared_ptr 可能出现的 race condition

    考虑一个简单的场景,有 3 个 shared_ptr<Foo> 对象 x、g、n:

    • shared_ptr<Foo> g(new Foo); // 线程之间共享的 shared_ptr
    • shared_ptr<Foo> x; // 线程 A 的局部变量
    • shared_ptr<Foo> n(new Foo); // 线程 B 的局部变量

    一开始,各安其事。

    sp5

    线程 A 执行 x = g; (即 read g),以下完成了步骤 1,还没来及执行步骤 2。这时切换到了 B 线程。

    sp6

    同时编程 B 执行 g = n; (即 write g),两个步骤一起完成了。

    先是步骤 1:

    sp7

    再是步骤 2:

    sp8

    这是 Foo1 对象已经销毁,x.ptr 成了空悬指针!

    最后回到线程 A,完成步骤 2:

    sp9

    多线程无保护地读写 g,造成了“x 是空悬指针”的后果。这正是多线程读写同一个 shared_ptr 必须加锁的原因。

    当然,race condition 远不止这一种,其他线程交织(interweaving)有可能会造成其他错误。

    思考,假如 shared_ptr 的 operator= 实现是先复制 ref_count(步骤 2)再复制 ptr(步骤 1),会有哪些 race condition?

    杂项

    shared_ptr 作为 unordered_map 的 key

    如果把 boost::shared_ptr 放到 unordered_set 中,或者用于 unordered_map 的 key,那么要小心 hash table 退化为链表。http://stackoverflow.com/questions/6404765/c-shared-ptr-as-unordered-sets-key/12122314#12122314

    直到 Boost 1.47.0 发布之前,unordered_set<std::shared_ptr<T> > 虽然可以编译通过,但是其 hash_value 是 shared_ptr 隐式转换为 bool 的结果。也就是说,如果不自定义hash函数,那么 unordered_{set/map} 会退化为链表。https://svn.boost.org/trac/boost/ticket/5216

    Boost 1.51 在 boost/functional/hash/extensions.hpp 中增加了有关重载,现在只要包含这个头文件就能安全高效地使用 unordered_set<std::shared_ptr> 了。

    这也是 muduo 的 examples/idleconnection 示例要自己定义 hash_value(const boost::shared_ptr<T>& x) 函数的原因(书第 7.10.2 节,p.255)。因为 Debian 6 Squeeze、Ubuntu 10.04 LTS 里的 boost 版本都有这个 bug。

    为什么图 1 中的 ref_count 也有指向 Foo 的指针?

    shared_ptr<Foo> sp(new Foo) 在构造 sp 的时候捕获了 Foo 的析构行为。实际上 shared_ptr.ptr 和 ref_count.ptr 可以是不同的类型(只要它们之间存在隐式转换),这是 shared_ptr 的一大功能。分 3 点来说:

    1. 无需虚析构;假设 Bar 是 Foo 的基类,但是 Bar 和 Foo 都没有虚析构。

    shared_ptr<Foo> sp1(new Foo); // ref_count.ptr 的类型是 Foo*

    shared_ptr<Bar> sp2 = sp1; // 可以赋值,自动向上转型(up-cast)

    sp1.reset(); // 这时 Foo 对象的引用计数降为 1

    此后 sp2 仍然能安全地管理 Foo 对象的生命期,并安全完整地释放 Foo,因为其 ref_count 记住了 Foo 的实际类型。

    2. shared_ptr<void> 可以指向并安全地管理(析构或防止析构)任何对象;muduo::net::Channel class 的 tie() 函数就使用了这一特性,防止对象过早析构,见书 7.15.3 节。

    shared_ptr<Foo> sp1(new Foo); // ref_count.ptr 的类型是 Foo*

    shared_ptr<void> sp2 = sp1; // 可以赋值,Foo* 向 void* 自动转型

    sp1.reset(); // 这时 Foo 对象的引用计数降为 1

    此后 sp2 仍然能安全地管理 Foo 对象的生命期,并安全完整地释放 Foo,不会出现 delete void* 的情况,因为 delete 的是 ref_count.ptr,不是 sp2.ptr。

    3. 多继承。假设 Bar 是 Foo 的多个基类之一,那么:

    shared_ptr<Foo> sp1(new Foo);

    shared_ptr<Bar> sp2 = sp1; // 这时 sp1.ptr 和 sp2.ptr 可能指向不同的地址,因为 Bar subobject 在 Foo object 中的 offset 可能不为0。

    sp1.reset(); // 此时 Foo 对象的引用计数降为 1

    但是 sp2 仍然能安全地管理 Foo 对象的生命期,并安全完整地释放 Foo,因为 delete 的不是 Bar*,而是原来的 Foo*。换句话说,sp2.ptr 和 ref_count.ptr 可能具有不同的值(当然它们的类型也不同)。

    为什么要尽量使用 make_shared()?

    为了节省一次内存分配,原来 shared_ptr<Foo> x(new Foo); 需要为 Foo 和 ref_count 各分配一次内存,现在用 make_shared() 的话,可以一次分配一块足够大的内存,供 Foo 和 ref_count 对象容身。数据结构是:

    sp10

    不过 Foo 的构造函数参数要传给 make_shared(),后者再传给 Foo::Foo(),这只有在 C++11 里通过 perfect forwarding 才能完美解决。

    (.完.)

    展开全文
  •   那什么时候需要加锁呢,就是当线程同时操作一个变量时,就需要加锁了。至于为什么要加锁,可以看看文顶顶的这篇文章:http://www.cnblogs.com/wendingding/p/3805841.html, 写的非常明白。读本篇文章之前...

    我在上一篇文章讲了线程的生命周期,这篇文章来讲讲线程加锁的注意事项与@synchronized关键字。

      那什么时候需要加锁呢,就是当多条线程同时操作一个变量时,就需要加锁了。至于为什么要加锁,可以看看文顶顶的这篇文章:http://www.cnblogs.com/wendingding/p/3805841.html, 写的非常明白。读本篇文章之前建议读一下。

    上代码
    声明变量

    @interface ViewController ()
    @property (strong, nonatomic)NSThread *thread1;
    @property (strong, nonatomic)NSThread *thread2;
    @property (strong, nonatomic)NSThread *thread3;
    @property (assign, nonatomic)int leftTickets;
    @end

    实现代码

    - (void)viewDidLoad {
        [super viewDidLoad];
        
        self.thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets) object:nil];
        self.thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets) object:nil];
        self.thread3 = [[NSThread alloc] initWithTarget:self selector:@selector(sellTickets) object:nil];
        self.thread1.name = @"thread1";
        self.thread2.name = @"thread2";
        self.thread3.name = @"thread3";
        // 总票数
        self.leftTickets = 10;
    }
    // 点击屏幕开启线程
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        [self.thread1 start];
        [self.thread2 start];
        [self.thread3 start];
    }
    - (void)sellTickets {
        while (1) {
            @synchronized (self) {
                if (self.leftTickets > 0) {
                    [NSThread sleepForTimeInterval:0.2];
                    int count = self.leftTickets;
                    self.leftTickets = count - 1;
                    NSLog(@"剩余的票数%d",self.leftTickets);
                    NSLog(@"当前线程=%@", [NSThread currentThread]);
                }else {
                    NSLog(@"票卖完了");
                    NSLog(@"退出线程%@",[NSThread currentThread]);
                    [NSThread exit];
                }
            }
        }
    }

    打印日志

    2016-11-04 11:52:25.117 TTTTTTTTTT[6753:74162] 剩余的票数9
    2016-11-04 11:52:25.117 TTTTTTTTTT[6753:74162] 当前线程=<NSThread: 0x608000073880>{number = 3, name = thread1}
    2016-11-04 11:52:25.393 TTTTTTTTTT[6753:74163] 剩余的票数8
    2016-11-04 11:52:25.393 TTTTTTTTTT[6753:74163] 当前线程=<NSThread: 0x608000074540>{number = 4, name = thread2}
    2016-11-04 11:52:25.661 TTTTTTTTTT[6753:74164] 剩余的票数7
    2016-11-04 11:52:25.661 TTTTTTTTTT[6753:74164] 当前线程=<NSThread: 0x608000074580>{number = 5, name = thread3}
    2016-11-04 11:52:25.932 TTTTTTTTTT[6753:74162] 剩余的票数6
    2016-11-04 11:52:25.933 TTTTTTTTTT[6753:74162] 当前线程=<NSThread: 0x608000073880>{number = 3, name = thread1}
    2016-11-04 11:52:26.164 TTTTTTTTTT[6753:74163] 剩余的票数5
    2016-11-04 11:52:26.165 TTTTTTTTTT[6753:74163] 当前线程=<NSThread: 0x608000074540>{number = 4, name = thread2}
    2016-11-04 11:52:26.438 TTTTTTTTTT[6753:74164] 剩余的票数4
    2016-11-04 11:52:26.439 TTTTTTTTTT[6753:74164] 当前线程=<NSThread: 0x608000074580>{number = 5, name = thread3}
    2016-11-04 11:52:26.704 TTTTTTTTTT[6753:74162] 剩余的票数3
    2016-11-04 11:52:26.705 TTTTTTTTTT[6753:74162] 当前线程=<NSThread: 0x608000073880>{number = 3, name = thread1}
    2016-11-04 11:52:26.975 TTTTTTTTTT[6753:74163] 剩余的票数2
    2016-11-04 11:52:26.976 TTTTTTTTTT[6753:74163] 当前线程=<NSThread: 0x608000074540>{number = 4, name = thread2}
    2016-11-04 11:52:27.232 TTTTTTTTTT[6753:74164] 剩余的票数1
    2016-11-04 11:52:27.233 TTTTTTTTTT[6753:74164] 当前线程=<NSThread: 0x608000074580>{number = 5, name = thread3}
    2016-11-04 11:52:27.505 TTTTTTTTTT[6753:74162] 剩余的票数0
    2016-11-04 11:52:27.505 TTTTTTTTTT[6753:74162] 当前线程=<NSThread: 0x608000073880>{number = 3, name = thread1}
    2016-11-04 11:52:27.505 TTTTTTTTTT[6753:74163] 票卖完了
    2016-11-04 11:52:27.506 TTTTTTTTTT[6753:74163] 退出线程<NSThread: 0x608000074540>{number = 4, name = thread2}

      
      我们一般用@synchronized来给线程加锁。它有什么用呢:
    (1) 堵塞所在线程,线程里面剩下的任务只有当@synchronized里面的代码执行完毕才能继续往下执行,和队列的同步差不多是一个意思。
    ​(2)当执行@synchronized里面的代码之前,所在线程要先检查是否有其他的线程执行里面的代码。如果没有,才继续往下执行。
    再看打印日志里面最后一条,说明了只有线程“thread3”退出了,其他的线程没有退出。

      我上篇文章讲,不用管线程的退出,任务执行完线程会自动退出。但是这是一个while循环啊!如果不退出线程,线程会一直执行。
    代码

    - (void)sellTickets {
        while (1) {
            @synchronized (self) {
                if (self.leftTickets > 0) {
                    [NSThread sleepForTimeInterval:0.2];
                    int count = self.leftTickets;
                    self.leftTickets = count - 1;
                    NSLog(@"剩余的票数%d",self.leftTickets);
                    NSLog(@"当前线程=%@", [NSThread currentThread]);
                }else {
                    NSLog(@"票卖完了");
                    NSLog(@"退出线程%@",[NSThread currentThread]);
                    // 不让线程退出
                    //[NSThread exit];
                }
            }
        }
    }

    打印的日志

    2016-11-04 12:01:53.309 TTTTTTTTTT[7110:78974] 当前线程=<NSThread: 0x600000076f40>{number = 4, name = thread2}
    2016-11-04 12:01:53.556 TTTTTTTTTT[7110:78973] 剩余的票数0
    2016-11-04 12:01:53.556 TTTTTTTTTT[7110:78973] 当前线程=<NSThread: 0x600000076fc0>{number = 3, name = thread1}
    2016-11-04 12:01:53.556 TTTTTTTTTT[7110:78975] 票卖完了
    2016-11-04 12:01:53.557 TTTTTTTTTT[7110:78975] 退出线程<NSThread: 0x600000077240>{number = 5, name = thread3}
    2016-11-04 12:01:53.558 TTTTTTTTTT[7110:78974] 票卖完了
    2016-11-04 12:01:53.559 TTTTTTTTTT[7110:78974] 退出线程<NSThread: 0x600000076f40>{number = 4, name = thread2}
    2016-11-04 12:01:53.560 TTTTTTTTTT[7110:78973] 票卖完了

      那又为什么只有线程thread2退出呢?(注:每次退出的线程是不确定的)因为当线程thread2退出了,并没有执行完@synchronized里的方法,线程thread1和线程thread3还在等thread2执行完了,它们好去执行呢。但是线程thread2已经死了,不可能再执行了。这就造成线程thread1和线程thread3一直都在内存里,没有被退出,造成了CPU不必要的开销,所以我们最好不要在@synchronized里面退出线程。

    - (void)sellTickets {
        while (1) {
            @synchronized (self) {
                if (self.leftTickets > 0) {
                    [NSThread sleepForTimeInterval:0.2];
                    int count = self.leftTickets;
                    self.leftTickets = count - 1;
                    NSLog(@"剩余的票数%d",self.leftTickets);
                    NSLog(@"当前线程=%@", [NSThread currentThread]);
                }
            }
            if (self.leftTickets == 0) {
                NSLog(@"票卖完了");
                NSLog(@"退出线程%@",[NSThread currentThread]);
                [NSThread exit];
            }
        }
    }
    
    2016-11-04 12:06:51.795 TTTTTTTTTT[7295:81206] 票卖完了
    2016-11-04 12:06:51.795 TTTTTTTTTT[7295:81207] 票卖完了
    2016-11-04 12:06:51.795 TTTTTTTTTT[7295:81208] 票卖完了
    2016-11-04 12:06:51.796 TTTTTTTTTT[7295:81206] 退出线程<NSThread: 0x60000026a3c0>{number = 3, name = thread1}
    2016-11-04 12:06:51.796 TTTTTTTTTT[7295:81207] 退出线程<NSThread: 0x60000026a380>{number = 4, name = thread2}
    2016-11-04 12:06:51.796 TTTTTTTTTT[7295:81208] 退出线程<NSThread: 0x60000026a740>{number = 5, name = thread3}

      这就是NSThread加锁以及加锁的一些注意事项。如果感觉对你有用,记得关注啊,我会每周分享一些技术心得。

    转载于:https://www.cnblogs.com/doujiangyoutiao/p/6029769.html

    展开全文
  • } } 补充一个关于volitail小知识: 伪共享问题:volitail 会触发缓存一致性[intel用的是MESI协议],缓存一致性每次都是按照行为单位触发的(缓存行的概念),行内可能有变量比如A,B 一旦A失效整个行的变量都将...
  • Go语言中 sync 包里提供了互斥锁 Mutex 和读写锁 RWMutex 用于处理并发过程中可能出现同时两个或个协程(或线程)读或写同一个变量的情况。 为什么需要锁 锁是 sync 包中的核心,它主要有两个方法,分别是加锁...
  • 进程是操作系统进行资源分配和调度的基本概念,可以说进程是线程的容器,一个进程里包括数据区域和堆栈存储着活动过程调用的指令和本地的变量,进程没运行时候处于没有生命的一个实体的状态,运行时依赖处理器给他...
  • 进程是操作系统进行资源分配和调度的基本概念,可以说进程是线程的容器,一个进程里包括数据区域和堆栈存储着活动过程调用的指令和本地的变量,进程没运行时候处于没有生命的一个实体的状态,运行时依赖处理器给他...
  • synchronize:保证原子性、可见性、有序性(多线程顺序执行) volatile:保证可见性、有序性(禁止指令重排序) public class Singleton { private static volatile Singleton s;//1,volatile修饰的必要性 ...
  • ★线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。举一个例子:小明和小红同时...
  • 多线程编程中最难控制的就是...当所有线程共享一些变量或者对象时,一定对改变此变量或对象的代码段加锁。 namespace CheckedAndUnchecked { class Program private static int Count = 0; static void Main(st
  • 本身不是锁,满足某个条件,像加锁一样,造成阻塞,与互斥量配合,给多线程提供会所。 为什么要用条件变量: 在线程抢占互斥锁时,线程A抢到了互斥锁,但是条件不满足,线程A就会让出互斥锁让给其他线程,然后等待...
  • 多线程同步理解

    2018-05-15 15:15:01
    一、为什么要多线程的概念? 多线程概念的提出,主要是用于解决并发问题(全局变量和局部变量),多线程概念也就是模仿多个人访问的场景。可以这样理解,多个人通过浏览器访问tomcat服务端,tomcat服务端接收到的...
  • 在C语言编程中,我们都知道,数据共享往往是通过全局变量的方式,全局变量在单线程中不会有什么问题,但是在多线程情况下,如果多个线程都对这个全局变量进行读写操作,需要加锁进行保护,锁的代价其实是很大的。...
  • 了解ThreadLocal,首先搞清楚ThreadLocal 是...我们知道有时候一个对象的变量会被线程所访问,这时就会有线程安全问题,当然我们可以使用synchorinized 关键字来为此变量加锁,进行同步处理,从而限制只能有一个
  • Linux多线程同步

    2020-10-06 21:43:42
    Linux下多线程同步的方式主要有互斥量(互斥锁)、条件变量、信号量以及读写锁等,更详细的可以看《UNIX环境高级编程第11章》。 线程同步的作用是什么?为什么要线程同步? 关于线程同步,我认为其实并不全是原文说...
  • 多线程之synchronized锁

    2019-09-18 23:42:08
    多线程之synchronized锁 1.为什么要锁? 当对共享变量进行操作时,不加锁会造成混乱,因为每个线程都能对共享变量进行更改,加了锁,则同一时间只有一个线程能对共享变量进行操作,其他的线程都会阻塞,等待锁释放...
  • 什么要有同步互斥:线程都是抢占式执行; 互斥 通过加锁的方式让一个线程只能执行一次临界区 互斥锁 先加锁 pthread_mutex_init(&mutex,MULL) 执行临界区代码 释放锁 pthread_mutex_destroy(&...
  • Java并发-多线程(2)

    2018-12-28 23:19:11
    一、线程的同步 1.什么线程的同步 ...使用同步代码块:synchronized(要加锁变量){作用的代码块范围} // 子线程类 public static class SonThread extends Thread { private Object a...
  • 线程同时共享同一一个全局变量做写的操作时候,可能会受到其他线程的手扰,就会产生线程安全问题,导致数据脏读。 2.如何解决线程安全问题 3. Synchronized加锁之后如何保证线程安全问题: 举个例子: 如果A...
  •  多线程可以提高效率,但是在共享变量的情况下,可能出现混乱的情况,比如A线程将共享变量 m=5,但是B线程在用m变量的时候,出现了混乱。这样多个线程共同访问同样的一个资源,你争我夺,一团乱。线程的不安全现象...
  • 多线程中互斥锁与信号量的区别

    千次阅读 2018-07-25 14:55:14
    在使用多线程的过程中对于互斥锁和信号量使用比较迷糊,不知道二者有什么区别,都能保证线程互斥,现做以下说明: “信号量是一个线程完成了某...比如对全局变量的访问,有时要加锁,操作完了,在解锁。有的时候锁...
  • 假想在这样的情况下,线程需要等待某个条件才能继续工作(如生产者消费者模型中,消费者需要等待流水线上有产品后才能消费),如果只使用互拆锁,则线程要不停的查询流水线是否为空这个状态,并且查询这个...
  • 它是什么? 它是一个线程的副本变量工具类,[工具类]java.lang....如果说各个线程的操作互不影响,加锁又消耗效率,那么复制一份就是解决办法,由于java是引用类型,所以复制不简单,可能序列化才能高度,所以这...
  • Linux多线程(三)(同步互斥) 1. 线程的同步与互斥 1.1....mutex是一种简单的加锁的方法来控制对共享资源的存取,这...为什么需要加锁,就是因为多个线程共用进程的资源,访问的是公共区间时(全局变量...
  • 首先,我们先了解在使用多线程的时候,什么情况下需要加锁? 那是因为在不同的线程,我们可能会需要对同一个变量进行修改,这个时候就会出现资源抢占的问题,比如在线程A中,对变量X的修改还未执行完毕时,这个时候...
  • 多线程访问同一个变量,我们需要加锁,而锁是比较消耗性能的,JDk1.5之后,​ 新增的原子操作类提供了一种用法简单、性能高效、线程安全地更新一个变量的方式,​ 这些类同样位于JUC包下的atomic包下,发展到JDk...
  • Java 内存模型是通过各种操作来定义,包括对变量的读/写操作,监视器的加锁和释放操作,以及线程启动和合并操作。JMM为程序中所有的操作定义了一个偏序关系,称之为Happens-Before。想保证操作B的线程能看到操作A...
  • 什么要引入同步机制? 在多线程环境中,可能会有两个甚至更多的线程试图同时访问一个有限的资源。必须对这种 潜在资源冲突进行预防。 解决方法:在线程使用一个资源时为其加锁即可。访问资源的第一个线程为...

空空如也

空空如也

1 2 3 4 5 ... 7
收藏数 133
精华内容 53
关键字:

多线程什么变量要加锁