澳门金莎娱乐网站程序并发时尊崇分享数据的难

作者: 数据库信息  发布:2019-11-28

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

 我们先通过一个简单的代码来了解该问题。
同步问题

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

我们使用一个简单的结构体 Counter,该结构体包含一个值以及一个方法用来改变这个值:  

#include <iostream>
#include <string>
#include <thread>
#include <vector>

using std::thread;
using std::vector;
using std::cout;
using std::endl;

class Incrementer
{
  private:
    int counter;

  public:
    Incrementer() : counter{0} { };

    void operator()()
    {
      for(int i = 0; i < 100000; i  )
      {
        this->counter  ;
      }
    }

    int getCounter() const
    {
      return this->counter;
    }   
};

int main()
{
  // Create the threads which will each do some counting
  vector<thread> threads;

  Incrementer counter;

  threads.push_back(thread(std::ref(counter)));
  threads.push_back(thread(std::ref(counter)));
  threads.push_back(thread(std::ref(counter)));

  for(auto &t : threads)
  {
    t.join();
  }

  cout << counter.getCounter() << endl;

  return 0;
}
struct Counter {
  int value;

  void increment(){
      value;
  }
};

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

然后启动多个线程来修改结构体的值:

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

 

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

int main(){
  Counter counter;

  std::vector<std::thread> threads;
  for(int i = 0; i < 5;   i){
    threads.push_back(std::thread([&counter](){
      for(int i = 0; i < 100;   i){
        counter.increment();
      }
    }));
  }

  for(auto& thread : threads){
    thread.join();
  }

  std::cout << counter.value << std::endl;

  return 0;
}

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

我们启动了5个线程来增加计数器的值,每个线程增加了100次,然后在线程结束时打印计数器的值。

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

但我们运行这个程序的时候,我们是希望它会答应500,但事实不是如此,没人能确切知道程序将打印什么结果,下面是在我机器上运行后打印的数据,而且每次都不同:  

g   -std=c  11 -lpthread -o threading_example main.cpp
442
500
477
400
422
487

运行结果:

问题的原因在于改变计数器值并不是一个原子操作,需要经过下面三个操作才能完成一次计数器的增加:

 

  •     首先读取 value 的值
  •     然后将 value 值加1
  •     将新的值赋值给 value
[lucas@lucas-desktop src]$ ./threading_example
218141
[lucas@lucas-desktop src]$ ./threading_example
208079
[lucas@lucas-desktop src]$ ./threading_example
100000
[lucas@lucas-desktop src]$ ./threading_example
202426
[lucas@lucas-desktop src]$ ./threading_example
172209

但你使用单线程来运行这个程序的时候当然没有任何问题,因此程序是顺序执行的,但在多线程环境中就有麻烦了,想象下下面这个执行顺序:

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

  •     Thread 1 : 读取 value, 得到 0, 加 1, 因此 value = 1
  •     Thread 2 : 读取 value, 得到 0, 加 1, 因此 value = 1
  •     Thread 1 : 将 1 赋值给 value,然后返回 1
  •     Thread 2 : 将 1 赋值给 value,然后返回 1
movl  counter(%rip), 
					

本文由金沙澳门官网发布于数据库信息,转载请注明出处:澳门金莎娱乐网站程序并发时尊崇分享数据的难

关键词: 金沙澳门官网