看流星社区

 找回密码
 注册账号
查看: 2086|回复: 0

多线程数据同步

[复制链接]

该用户从未签到

发表于 2016-9-11 18:01:50 | 显示全部楼层 |阅读模式
前言:

什么是线程安全:在两个或者多个线程同时访问数据时,数据不会被破坏,而并不一定是需要获取线程的同步锁。

理解原子操作:原子操作是指我们在编程当中,定义的一个或者一组操作,这个、组操作类似于SQLServer当中事务的概念,整个过程是不可分割的。是操作的最小分割单元,即这个操作或者这组操作不可再分。系统支持一些默认的原子操作,如保证一个整型值的赋值过程是一次成型没有中间状态的,如int x=5;这个赋值语句不会由于其它线程访问x而造成本次的操作(x=5)之后x为(0,1,2,3,4,5)等中间状态的值。

原子操作产生的问题:系统仅对常见数据类型做了原子操作保护,或者只对共享数据的单条读写语句做了原子界限设置。而我们的应用中往往有更多的操作,一组的修改动作需要把其封装到一个原子界限以内。而且即使系统默认对单条读写语句做了原子界限,但是依然未对共享的数据进行保护,使这个数据之被当前线程暂时拥有。

于是,对一组操作的原子界限设置,原子界限内的数据安全这两个诉求催生出多线程数据同步的各种模式。

一、“需要同步”这个需求的产生:

第一种情况:多个线程同时读写一个数据。

int x = 0;
ThreadPool.QueueUserWorkItem(state =>
{
    Console.WriteLine("1_1:" + x);
    for (int i = 0; i < 100000; i++)
    {
        x++;
    }
    Console.WriteLine("1_2:" + x);
}, null);

ThreadPool.QueueUserWorkItem(state =>
{
    Console.WriteLine("2_1:" + x);
    for (int i = 0; i < 100000; i++)
    {
        x++;
    }
    Console.WriteLine("2_2:" + x);
}, null);
Console.WriteLine("Main:" + x);

我们设计上述程序的希望是最终x的值等于20万,然而结果去很难达到20万。

原因在于:在某一时刻,第一个线程拿到共享数据历史值去修改还未修改完毕回写之前,第二个线程拿到了共享数据的历史值也去修改,结果导致谁最后回写共享数据谁的操作就有效,而由于另一个操作太快速导致了他的修改是无效的。

二、多线程同步问题产生环境的共有特点:

1、问题总是由循环结构引起:如果一个多线程的应用不包含至少一个很耗时的循环结构,这个应用一般是不需要数据同步的,这个应用很可能只是一个顺序执行应用,而非并行执行的应用,多线程在此起得作用仅仅是保持主线程的可响应状态。如果每个子线程中包含的操作只耗费很短的时间(可能未包含耗时的循环结构),那么即使这样的子线程多大上10个,也是毫无意义的。完全可以把它们放在主线程里面顺序执行,或者归到一个子线程里面去顺序执行即可。总之使用多线程是很昂贵的,如非非常必须,使用单线程或者减少线程数量才是上策。

2、我们总是有“按顺序来”的强烈欲望:在多个线程同时读、写一个共享数据(集)的时候,每个线程的读和写的具体时刻(可理解为对数据、集的原子操作时刻)

三、使用同步锁出现的问题:

1、代码结构繁琐、难于调试。

2、损害性能:获取和释放锁都需要CPU时间,CPU需要协调锁。

3、只允许一个线程访问资源,其它线程阻塞,如果使用的是线程池技术,就会在线程池中生存新的线程用于相应,而当阻塞的线程唤醒时,CPU会面临更多的线程,CPU的线程上下文切换会造成性能损失。

解决的方法:

1、尽量避免多线程同步,采用其它方案避免资源竞争。

2、尽量避免使用静态自动、共享数据。

3、尽量使用值类型。
点击按钮快速添加回复内容: 支持 高兴 激动 给力 加油 苦寻 生气 回帖 路过 感恩
您需要登录后才可以回帖 登录 | 注册账号

本版积分规则

小黑屋|手机版|Archiver|看流星社区 |网站地图

GMT+8, 2024-3-19 15:28

Powered by Kanliuxing X3.4

© 2010-2019 kanliuxing.com

快速回复 返回顶部 返回列表