新手问题 关于多线程的基础问题

vianvio · 2014年10月14日 · 最后由 vianvio 回复于 2014年10月14日 · 2638 次阅读

小弟虽然写了挺久代码,但是多线程一直未涉及,总觉得很神秘。现在自学 ruby 写了一个数据分析的程序,因为数据需要不断重复循环搜索和总结,所以想着正好试着用上多线程。 于是就遇到基础问题了,首先是变量共享问题,比如下列代码

for i in 1..10
    arrThread = Array.new
    for j in i * 10...i * 10 + 3

        p "j = #{j}"

        arrThread << Thread.new { k = j; p "k = #{k}"; p k;}
    end
    #hold the main thread
    arrThread[-1].join
end

一开始我没有加入 k = j 这句,发现输出全是重复的,于是我意识到可能是变量 j 在 Thread.new 的时候已经变了的关系,所以我尝试在 block 里增加一个 k。可是结果任然存在重复的情况。。。为什么在各自 block 中的 k 也是共享的而不是每个 block 私有的?

第二个问题是这样的,同样,上代码

arrN = Array.new
rangeSize = 1..(iIndex/10).to_i
arrDataForSearch = arrayToCreate[0..iIndex - 1]
rangeSize.to_a.each do |v|
    arrN.push(ColorHelper.findN(5 * v, arrDataForSearch, true))
    t1 = Thread.new {arrN.push(ColorHelper.findN(5 * v - 4, arrDataForSearch, true))}
    t2 = Thread.new {arrN.push(ColorHelper.findN(5 * v - 3, arrDataForSearch, true))}
    t3 = Thread.new {arrN.push(ColorHelper.findN(5 * v - 2, arrDataForSearch, true))}
    t4 = Thread.new {arrN.push(ColorHelper.findN(5 * v - 1, arrDataForSearch, true))}
end

这段代码中我希望将原本逐个进入 findN 方法的循环,在单次循环中开 4 个额外线程并行进入。findN 方法是对整个 arrDataForSearch 数组做遍历并且匹配查找,没有数据修改。 结果发现并行确实存在,可是效率上并没有提升。单线程走完基本上在 1 分 05 秒,而多线程发挥不稳定,有时在 1 分,绝大多数在 1 分 20 秒,反而慢了。。 所以感觉是自己对多线程的理解和思路上肯定有问题,特请赐教,谢谢!

个人理解:Thread 是共用内存,而 j 是在 thread 外定义的,所以 thread 执行的时候去会去读 j,但是 j 作为循环变量,一直在变,所以在 thread 中读取 j 要看执行的时候 j 增加到什么值了。k 是由 j 赋值的,j 重复,k 当然也重复了。

#1 楼 @loveltyoic 嗯,的确是,回头翻了一下书,ruby 的变量直接等于的话只是换个马甲而已。测试下来只能在每一次子循环中增加 join 才能将不同的 j 赋值给 k,所以看来只能写几行重复的了,否则主线程会在赋值语句执行之前,把 j 的循环走完,而在循环中直接写入 join 又与顺序执行一样。 第二个问题能否指点一下呢?

#2 楼 @vianvio 先贴个链接https://ruby-china.org/topics/20367 事先声明,我也没并行编程经验。。。只是根据一些文章揣测。 用 jruby 跑一下看看有差别没,有可能是锁的问题?

第一个 因为 block 生成的是闭包,无论变量是什么类型一律是引用而非拷贝,所以会出现重复。可以将变量以参数的方式传进去:

arrThread << Thread.new(j) { |k| p "k = #{k}"; p k;}

第二个还没看懂代码的意思...

这个文章讲的很清楚了:http://www.jstorimer.com/blogs/workingwithcode/8085491-nobody-understands-the-gil

因为 MRI 有 GIL,楼主的 thread 都是针对一个 arr 操作,所以会触发锁,因此并不会真正的并发执行。

#5 楼 @loveltyoic 非常感谢,文章已保存,我尝试了一下在 thread 操作之前将 arr 进行了 Marshal.load(Marshal.dump(arr)),不过效率仍然没有很大提升,所以 MRI 的锁应该还是文章里所说的是固定只有一个进程在走,与是否操作同一个对象好像没有关系。我试了一下用 jruby,速度快了一倍,还挺有成就感的哈。不过对线程安全问题还得再深入一下,不过这是后话了

#4 楼 @saiga 原来如此,尝试了一下的确是这样,非常感谢~

需要 登录 后方可回复, 如果你还没有账号请 注册新账号