惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

大猫的无限游戏
大猫的无限游戏
S
SegmentFault 最新的问题
量子位
A
Arctic Wolf
L
Lohrmann on Cybersecurity
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
WordPress大学
WordPress大学
V
Vulnerabilities – Threatpost
博客园 - Franky
C
Cyber Attacks, Cyber Crime and Cyber Security
The Cloudflare Blog
Last Week in AI
Last Week in AI
The Hacker News
The Hacker News
I
Intezer
J
Java Code Geeks
P
Privacy International News Feed
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
S
Secure Thoughts
Cisco Talos Blog
Cisco Talos Blog
阮一峰的网络日志
阮一峰的网络日志
S
Securelist
Security Latest
Security Latest
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
小众软件
小众软件
Jina AI
Jina AI
有赞技术团队
有赞技术团队
人人都是产品经理
人人都是产品经理
博客园_首页
酷 壳 – CoolShell
酷 壳 – CoolShell
T
The Exploit Database - CXSecurity.com
雷峰网
雷峰网
T
Tenable Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
P
Privacy & Cybersecurity Law Blog
Simon Willison's Weblog
Simon Willison's Weblog
博客园 - 【当耐特】
T
Threat Research - Cisco Blogs
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
MongoDB | Blog
MongoDB | Blog
D
DataBreaches.Net
N
News | PayPal Newsroom
Google Online Security Blog
Google Online Security Blog
K
Kaspersky official blog
H
Help Net Security
宝玉的分享
宝玉的分享
罗磊的独立博客
Webroot Blog
Webroot Blog
月光博客
月光博客
B
Blog RSS Feed
Recorded Future
Recorded Future

博客园 - Henry Cui

招聘高级.Net工程师 Node.js Web开发(二)认识Express(上) Node.js Web开发(一)从零开始 Html5 Step by Step(二) 本地存储 HTML 5 Step by Step(一) 拖放API Git学习笔记(一)初识Git DDD中的Specification模式 Silverlight 4之旅(三)数据绑定(中) Silverlight 4之旅(二)数据绑定(上) Silverlight 4之旅(一) .Net 4.0 Parallel 编程之旅 .Net 4.0 Parallel 编程(九)Task中的数据共享(下) .Net 4.0 Parallel编程(八)Task中的数据共享(中) .Net 4.0 Parallel 编程(六)Task(下) .Net 4.0 Parallel 编程(五)Task(中) .Net 4.0 Parallel 编程(四) Task(上) Entity Framework Code-First(下) Entity Framework Code-First(上) Entity Framework POCO T4模板使用
.Net 4.0 Parallel 编程(七)Task中的数据共享(上)
Henry Cui · 2011-04-07 · via 博客园 - Henry Cui

在前几篇文章中我们看过Parallel Loop以及Task 部分内容,而在多线程的编程中我们较为痛疼的就是多线程中的数据共享问题以及数据同步的问题。本篇Post以及后面的几篇Post中会探讨下TPL中数据共享的常见问题以及其常规的解决办法。

引例

首先我们来看下面的一段代码:

static void Main(string[] args)
{
    int Sum = 0;
    Task[] tasks = new Task[10];
    for (int i = 0; i < 10; i++)
    {
        tasks[i] = new Task(() =>
        {
            for (int j = 0; j < 1000; j++)
            {
                Sum = Sum + 1;
            }
        });
        tasks[i].Start();
    }
    Task.WaitAll(tasks);
    Console.WriteLine("Expected value {0}, Parallel value: {1}",
    10000, Sum);
    Console.ReadLine();
}

上面的代码我们期望是得到10个线程对Sum数据进行轮询累加,按照我们期望的情况,算出的结果应该是:10000,可是我们看看运行后的结果:

image

这是为什么呢?其实我们想想就会明白,主要是我们多个线程并行的进行了Sum的操作,我们可以通过一个时序图来看表示小两个线程同时操作Sum会出现的状况(下图中的表述可能不正确,请谅解):

image

在Task1获取到sum值为0,Task2也获取到0,而两个Task都执行了0+1的操作之后并记录入Sum中,而不是我们预期的线程之间各自计算后的信息也进行累加。在我们在并行编程时,像这类数据共享问题是非常常见的。在上面的问题中,我们可以通过顺序执行的来解决这个问题,但是这不是我们希望的,毕竟我们还是希望以并行的方式去处理。在TPL中提供了分离执行的方法来解决这个问题。

分离执行

我们可以通过Task中传递的state参数来进行隔离执行:

static void Main(string[] args)
{
    int Sum = 0;
    Task<int>[] tasks = new Task<int>[10];
    for (int i = 0; i < 10; i++)
    {
        tasks[i] = new Task<int>((obj) =>
        {
            var start = (int)obj;
            for (int j = 0; j < 1000; j++)
            {
                start = start + 1;
            }
            return start;
        }, Sum);
        tasks[i].Start();
    }
    Task.WaitAll(tasks);
    for (var i = 0; i < 10; i++)
    {
        Sum += tasks[i].Result;
    }
    Console.WriteLine("Expected value {0}, Parallel value: {1}",
    10000, Sum);
    Console.ReadLine();
}

上面的程序里面我们在每个Task执行时就不存在数据共享了,每个Task中计算自己值,最后我们汇总每个Task的Result。似乎我们的问题已经解决了,但是我们要知道在.Net 运行时里面并没有支持分离。也就是说,每次我们操作Task时,都要保证Task之间是没有数据共享的,确实是件麻烦的事。在.Net中提供了System.Threading.ThreadLocal来创建分离。

ThreadLocal

ThreadLocal是一种提供线程本地存储的类型,它可以给每个线程一个分离的实例,来提供每个线程单独的数据结果。上面的程序我们可以使用TreadLocal:

static void Main(string[] args)
{
    int Sum = 0;
    Task<int>[] tasks = new Task<int>[10];
    var tl = new ThreadLocal<int>();
    for (int i = 0; i < 10; i++)
    {
        tasks[i] = new Task<int>((obj) =>
        {
            tl.Value = (int)obj;
            for (int j = 0; j < 1000; j++)
            {
                tl.Value++;
            }
            return tl.Value;
        }, Sum);
        tasks[i].Start();
    }
    Task.WaitAll(tasks);
    for (var i = 0; i < 10; i++)
    {
        Sum += tasks[i].Result;
    }
    Console.WriteLine("Expected value {0}, Parallel value: {1}",
    10000, Sum);
    Console.ReadLine();
}

但是我们要注意的一点TreadLocal是针对每个线程的,不是针对每个Task的。在TreadLocal构造函数中提供了一个重载的方法,以让我们来设置每个线程Result初始值。我们通过下面的例子来看看:

static void Main(string[] args)
{
    int Sum = 0;
    Task<int>[] tasks = new Task<int>[10];
    var tl = new ThreadLocal<int>(() => {
        Console.WriteLine(Sum);
        return Sum;
    });
    for (int i = 0; i < 10; i++)
    {
        tasks[i] = new Task<int>(() =>
        {
            for (int j = 0; j < 1000; j++)
            {
                tl.Value++;
            }
            return tl.Value;
        });
        tasks[i].Start();
    }
    Task.WaitAll(tasks);
    for (var i = 0; i < 10; i++)
    {
        Sum += tasks[i].Result;
    }
    Console.WriteLine("Expected value {0}, Parallel value: {1}",
    10000, Sum);
    Console.ReadLine();
}

我们来看看执行的结果:

image

大家可能有疑问,我们是创建了10个Task,可是ThreadLocal只进行了4次初始化。我们要记住一点Task不是Thread,一个Tread中可能有多个Task。我Demo的机器是四核的,这里开启了四个线程去执行10个Task。之所以我们看到的结果会不对,是因为,在一个线程里面会有多个Task,每个Task之间计算时,第一Task计算出来的结果会传递给下一个Task,从而进行了一次累加,所以会出现计算出来的值不准确。所以ThreadLocal是魔鬼也是天使,我们在使用时也要额外注意。

总结

在本文中通过引例我们看到了多线程中数据共享的问题,以及了解了分离执行的办法,同时也了解了TreadLocal的原理,希望对您有用。下篇文章会就TPL中的数据共享问题进行继续地探讨。

alt