C++11 多线程:数据保护

企鹅博客
企鹅博客
企鹅博客
28892
文章
0
评论
2020年9月14日04:25:17 评论 3 views 2038字阅读6分47秒

在编写多线程程序时,多个线程同时访问某个共享资源,会导致同步的问题,这篇文章中我们将介绍 C++11 多线程编程中的数据保护。

数据丢失

让我们从一个简单的例子开始,请看如下代码:

 

01#include <iostream>
02#include <string>
03#include <thread>
04#include <vector>
05  
06using std::thread;
07using std::vector;
08using std::cout;
09using std::endl;
10  
11class Incrementer
12{
13    private:
14        int counter;
15  
16    public:
17        Incrementer() : counter{0} { };
18  
19        void operator()()
20        {
21            for(int i = 0; i < 100000; i++)
22            {
23                this->counter++;
24            }
25        }
26  
27        int getCounter() const
28        {
29            return this->counter;
30        }       
31};
32  
33int main()
34{
35    // Create the threads which will each do some counting
36    vector<thread> threads;
37  
38    Incrementer counter;
39  
40    threads.push_back(thread(std::ref(counter)));
41    threads.push_back(thread(std::ref(counter)));
42    threads.push_back(thread(std::ref(counter)));
43  
44    for(auto &t : threads)
45    {
46        t.join();
47    }
48  
49    cout << counter.getCounter() << endl;
50  
51    return 0;
52}

这个程序的目的就是数数,数到30万,某些傻叉程序员想要优化数数的过程,因此创建了三个线程,使用一个共享变量 counter,每个线程负责给这个变量增加10万计数。

这段代码创建了一个名为 Incrementer 的类,该类包含一个私有变量 counter,其构造器非常简单,只是将 counter 设置为 0.

紧接着是一个操作符重载,这意味着这个类的每个实例都是被当作一个简单函数来调用的。一般我们调用类的某个方法时会这样 object.fooMethod(),但现在你实际上是直接调用了对象,如 object(). 因为我们是在操作符重载函数中将整个对象传递给了线程类。最后是一个 getCounter 方法,返回 counter 变量的值。

再下来是程序的入口函数 main(),我们创建了三个线程,不过只创建了一个 Incrementer 类的实例,然后将这个实例传递给三个线程,注意这里使用了 std::ref ,这相当于是传递了实例的引用对象,而不是对象的拷贝。

现在让我们来看看程序执行的结果,如果这位傻叉程序员还够聪明的话,他会使用 GCC 4.7 或者更新版本,或者是 Clang 3.1 来进行编译,编译方法:

 

1g++ -std=c++11 -lpthread -o threading_example main.cpp

运行结果:

 

01[[email protected] src]$ ./threading_example 
02218141
03[[email protected] src]$ ./threading_example 
04208079
05[[email protected] src]$ ./threading_example 
06100000
07[[email protected] src]$ ./threading_example 
08202426
09[[email protected] src]$ ./threading_example 
10172209

但等等,不对啊,程序并没有数数到30万,有一次居然只数到10万,为什么会这样呢?好吧,加1操作对应实际的处理器指令其实包括:

 

1movl    counter(%rip), %eax
2addl    $1, %eax
3movl    %eax, counter(%rip)

首个指令将装载 counter 的值到 %eax 寄存器,紧接着寄存器的值增1,然后将寄存器的值移给内存中 counter 所在的地址。

继续阅读
weinxin
欢迎加入中国站长博客之家
本站的所有资源都会上传分享到博客之家,希望大家互相学习交流进步。
把C++类成员方法直接作为线程回调函数 Linux编程

把C++类成员方法直接作为线程回调函数

我以前写线程时要么老老实实照着声明写,要么使用C++类的静态成员函数来作为回调函数,经常会因为线程代码而破坏封装.之前虽然知道类成员函数的展开形式,但从没想过利用过它,昨天看深入ATL时无意中学会了这...
Struts1配置及调用过程实例详解 Linux编程

Struts1配置及调用过程实例详解

MVC架构是目前web开发中的经典架构,它的核心思想就是将业务代码和视图代码分离,能够有效的理清系统结构,降低系统复杂度和维护难度。在传统的java web开发中采用servlet作为控制器将视图和模...
Java利用smslib发送短信 Linux编程

Java利用smslib发送短信

  自己写一个小程序,我在Java1.6.0_10;smslib-v3.4.5下运行成功.   主要是以下几个类.   Level_Final_Serial.java:串口底层操作   Serial_...
匿名

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: