ruby多线程共享对象之线程安全

在进行多线程处理中共享对象,多个线程对同一个对象同时进行修改,有可能出现不一致的状态,使用时要注意。

例子:

test.rb

x = 0

10.times.map do |i|
  Thread.new do
    puts "before (#{ i }): #{ x }"
    x += 1
    puts "after (#{ i }): #{ x }"
  end
end.each(&:join)

puts "\ntotal: #{ x }"

执行 ruby test.rb

输出:

before (1): 0
after (1): 1
before (0): 1
after (0): 2
before (2): 2
after (2): 3
before (4): 3
after (4): 4
before (6): 4before (5): 3before (9): 3
before (8): 3before (7): 3
after (7): 6
before (3): 3

after (3): 7
after (9): 5

after (8): 8

after (6): 9after (5): 10

total: 10

你可能注意到了,在第四次循环,i = 3,输出:

before (3): 3 #x等于3
after (3): 7 #x等于7

Mutex是一个类,它实现了一个简单的一些共享资源的互斥访问的信号锁定。也就是说,只有一个线程可以持有锁在一个给定的时间。其他线程可能会选择等待锁线,变为可用,或者可能干脆选择错误,指示立即得到锁不能使用。
通过将所有访问一个互斥的控制下共享数据,我们可以确保一致性和原子操作,将代码放到Mutex对象的synchorize方法中。

修改test.rb文件内容如下:

x = 0
mutex = Mutex.new

10.times.map do |i|
  Thread.new do
    mutex.synchronize do
      puts "before (#{ i }): #{ x }"
      x += 1
      puts "after (#{ i }): #{ x }"
    end
  end
end.each(&:join)

puts "\ntotal: #{ x }"

执行 ruby test.rb

输出:

before (0): 0
after (0): 1
before (9): 1
after (9): 2
before (2): 2
after (2): 3
before (3): 3
after (3): 4
before (4): 4
after (4): 5
before (5): 5
after (5): 6
before (6): 6
after (6): 7
before (7): 7
after (7): 8
before (8): 8
after (8): 9
before (1): 9
after (1): 10

total: 10

下面是个计数器的例子:

app.rb文件内容如下:

class Counter
  attr_reader :total

  def initialize
    puts ‘initialized...‘
    @total = 0
    @mutex = Mutex.new
  end

  def increment!
    @mutex.synchronize { @total += 1 }
  end
end

class Application
  def counter
    @counter ||= Counter.new
  end

  def increment!
    counter.increment!
  end

  def total
    counter.total
  end
end

app = Application.new
10.times.map do |i|
  Thread.new do
    app.increment!
  end
end.each(&:join)
puts app.total

执行 ruby app.rb有时候会出现这种结果:

initialized...
initialized...
initialized...
initialized...
initialized...
initialized...
initialized...
initialized...initialized...

1

这种结果是错误的,Counter使用了安全线程,但是最终的应用却没有,因为我们使用了 ||=,它不是原子的。Application的两个实例看到的@counter都是nil,于是都是实例化Counter,所以结果就是错的。

正确的是将Application修改成这样:

class Application
  def initialize
    @counter = Counter.new
  end

  def counter
    @counter
  end

  def increment!
    counter.increment!
  end

  def total
    counter.total
  end
end

这样在Application实例化的时候就将Counter实例化了。

再次执行,结果如下:

initialized...
10

处理死锁:

当我们开始使用互斥对象的线程排斥,我们必须小心地避免死锁。死锁条件时所发生的所有线程正在等待获取另一个线程持有的资源。因为所有的线程被阻塞,他们不能释放持有的锁。因为他们不能释放的锁,没有其他线程可以获取这些锁。
条件变量是一个简单的相关资源和用于特定互斥锁的保护范围内的信号。当你需要的资源是不可用的,你等待一个条件变量。这一操作释放相应的互斥锁。当其他一些线程信号的资源是可用的,原来的线程来等待,同时恢复对临界区锁。

ConditionVariable类实现了支持线程同步的状态变量的功能。ConditionVariable对象就是把线程的等待条件具体化的产物。

mutex = Mutex.new
cv = ConditionVariable.new

Thread.start {
    mutex.synchronize {
      ...
      while (尚未满足条件时)
        cv.wait(m)
      end
      ...
    }
}

如上所示,若某线程尚未满足条件,就调用wait方法将其挂起,并让其他线程执行

Thread.start {
    mutex.synchronize {
      # 进行某些操作使上述条件得到满足
      cv.signal
    }
}

然后调用signal方法通知正在wait的线程上述条件已成立。这是比较典型的用法。

参考:http://lucaguidi.com/2014/03/27/thread-safety-with-ruby.html

时间: 2024-10-10 07:38:44

ruby多线程共享对象之线程安全的相关文章

JAVA笔记14__多线程共享数据(同步)/ 线程死锁 /

/** * 多线程共享数据 * 线程同步:多个线程在同一个时间段只能有一个线程执行其指定代码,其他线程要等待此线程完成之后才可以继续执行. * 多线程共享数据的安全问题,使用同步解决. * 线程同步两种方法: * 1.同步代码块 * synchronized(要同步的对象){ 要同步的操作 } * 2.同步方法 * public synchronized void method(){ 要同步的操作 } */ public class Main { public static void main(

线程系列03,多线程共享数据,多线程不共享数据

多线程编程,有时希望每个线程的数据相互隔离互不影响,有时却希望线程间能共享数据,并保持同步.本篇体验多线程共享和不共享数据. □ 多线程不共享数据 对于多线程,CLR到底是怎样为它们分配内存栈空间呢?是"一个萝卜一个坑",每个线程都有自己的栈空间:还是"大树底下好乘凉",所有的线程共享同一个栈空间? 我们让2个线程执行相同的静态方法,用到相同的变量,通过打印变量来求证多线程栈空间的分配情况. class Program { static void Main(stri

Java多线程编程基础之线程对象

在进入java平台的线程对象之前,基于基础篇(一)的一些问题,我先插入两个基本概念. [线程的并发与并行] 在单CPU系统中,系统调度在某一时刻只能让一个线程运行,虽然这种调试机制有多种形式(大多数是时间片轮巡为主),但无论如何,要通过不断切换需要运行的线程让其运行的方式就叫并发(concurrent).而在多CPU系统中,可以让两个以上的线程同时运行,这种可以同时让两个以上线程同时运行的方式叫做并行(parallel). 在上面包括以后的所有论述中,请各位朋友谅解,我无法用最准确的词语来定义储

并行编程之多线程共享非volatile变量,会不会可能导致线程while死循环

背景 大家都知道线程之间共享变量要用volatile关键字.但是,如果不用volatile来标识,会不会导致线程死循环?比如下面的伪代码: static int flag = -1; void thread1(){ while(flag > 0){ //wait or do something } } void thread2(){ //do something flag = -1; } 线程1,线程2同时运行,线程2退出之后,线程1会不会有可能因为缓存等原因,一直死循环? 真实的世界 第一个坑

Ruby多线程

Ruby多线程 Ruby多线程教程,编程教程,例子教程,参考手册和代码 - 传统程序有一个执行线程,包括程序的语句或指令顺序执行,直到程序终止. 传统程序中有一个执行线程,包括程序的语句或指令顺序执行,直到程序终止. 一个多线程程序中有多个执行线程.在每个线程中,语句顺序执行,但可parallel.on执行线程本身多核的CPU,例如.多个线程往往在一个单CPU的机器,不实际执行并行,但并行交错执行的线程模拟. Ruby可以很容易地写Thread类的多线程程序. Ruby线程是一个轻量级的和有效的

Java多线程(二) —— 线程安全、线程同步、线程间通信(含面试题集)

一.线程安全 多个线程在执行同一段代码的时候,每次的执行结果和单线程执行的结果都是一样的,不存在执行结果的二义性,就可以称作是线程安全的. 讲到线程安全问题,其实是指多线程环境下对共享资源的访问可能会引起此共享资源的不一致性.因此,为避免线程安全问题,应该避免多线程环境下对此共享资源的并发访问. 线程安全问题多是由全局变量和静态变量引起的,当多个线程对共享数据只执行读操作,不执行写操作时,一般是线程安全的:当多个线程都执行写操作时,需要考虑线程同步来解决线程安全问题. 二.线程同步(synchr

多线程创建方式及线程安全问题

1.创建线程方式 一:  创建线程方式一继承Thread类 public clsss MyThread extends Thread{ //重写run方法,设置线程任务 Run(){ } } main(){ new MyThread().start(); } 获取线程名称: Thread.currentThread()获取当前线程对象 Thread.currentThread().getName();获取当前线程对象的名称 二:创建线程方式-实现Runnable接口 创建线程的步骤. 1.定义类

多线程编程(进程和线程)

多线程编程(进程和线程) 1.进程:指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程可以启动多个线程. 2.线程:指程序中一个执行流程,一个进程中可以运行多个线程. 一.创建线程(两种方式) 二.线程的5种状态( New,Runnable,Running,Block,Dead ): 三.线程的优先级 四.守护线程 /精灵线程/后台线程 五.方法 六.同步代码锁(synchronized) 一.创建线程(两种方式): 方式1:采用继承Thread的方法 第一,继承 Thre

面试题_1_to_16_多线程、并发及线程的基础问题

多线程.并发及线程的基础问题 1)Java 中能创建 volatile 数组吗?能,Java 中可以创建 volatile 类型数组,不过只是一个指向数组的引用,而不是整个数组.我的意思是,如果改变引用指向的数组,将会受到 volatile 的保护,但是如果多个线程同时改变数组的元素,volatile 标示符就不能起到之前的保护作用了. 2)volatile 能使得一个非原子操作变成原子操作吗?一个典型的例子是在类中有一个 long 类型的成员变量.如果你知道该成员变量会被多个线程访问,如计数器