2009-07-03 27 views
59

हैश को लागू करने का सबसे अच्छा तरीका क्या है जिसे कई धागे में संशोधित किया जा सकता है, लेकिन छोटी संख्या में ताले के साथ। इस प्रश्न के प्रयोजनों के लिए, आप मान सकते हैं कि हैश पढ़ा जाएगा। यह सभी रूबी कार्यान्वयन में थ्रेड-सुरक्षित होना चाहिए, जिसमें वास्तव में एक साथ फैशन, जैसे जेआरबीबी में काम करना शामिल है, और इसे शुद्ध-रूबी (कोई सी या जावा अनुमति नहीं) में लिखा जाना चाहिए।शुद्ध-रूबी समवर्ती हैश

हमेशा एक ताकतवर समाधान प्रस्तुत करने के लिए स्वतंत्र महसूस करें जो हमेशा ताले लगाता है, लेकिन यह सबसे अच्छा समाधान होने की संभावना नहीं है। लालित्य के लिए अंक, लेकिन लॉकिंग की एक छोटी संभावना छोटे कोड पर जीत जाती है।

+0

रूबी 1.8 या 1.9? –

+1

मैं रुबी 1.9 पर कामों के मुकाबले एक जवाब स्वीकार करूंगा जब तक यह 1.9 मोड में जेआरबीई पर काम करता है। मैं 1.8 और 1.9 दोनों पर कामों की तुलना में एक उत्तर पसंद करता हूं। –

+1

यह कैसे उपयोगी है? निश्चित रूप से आप मूल्य को सुरक्षित रूप से प्राप्त कर चुके हैं, लेकिन कोई बात नहीं है कि मान कितना मान्य है। आप इसे हैश से बाहर खींच सकते हैं, दूसरा निर्धारित किया जा सकता है और उसी कुंजी को लिख सकता है, इससे पहले कि आप इसके साथ कुछ भी दिलचस्प करें। –

उत्तर

20

ठीक है, अब आपने 'थ्रेडसेफ' का वास्तव में अर्थ निर्दिष्ट किया है, यहां दो संभावित कार्यान्वयन हैं। निम्नलिखित कोड एमआरआई और जेआरबी में हमेशा के लिए चलाएगा।लॉकलेस कार्यान्वयन एक अंतिम स्थिरता मॉडल का पालन करता है जहां मास्टर थक्स में है, तो प्रत्येक थ्रेड हैश के अपने स्वयं के दृश्य का उपयोग करता है। थ्रेड में सभी जानकारी संग्रहीत करने के लिए एक छोटी सी चालबाजी की आवश्यकता होती है जो स्मृति को रिसाव नहीं करती है, लेकिन इसे संभाला और परीक्षण किया जाता है - प्रक्रिया कोड इस कोड को चलाने में वृद्धि नहीं करता है। दोनों कार्यान्वयनों को 'पूर्ण' होने का अधिक काम करने की आवश्यकता होगी, जिसका अर्थ है हटाना, अपडेट करना आदि को कुछ सोच की आवश्यकता होगी, लेकिन नीचे दी गई दो अवधारणाओं में से कोई भी आपकी आवश्यकताओं को पूरा करेगा।

इस धागे को पढ़ने के लिए लोगों के लिए यह बहुत महत्वपूर्ण है कि पूरे मुद्दे को JRuby के लिए विशिष्ट है - एमआरआई में अंतर्निहित हैश पर्याप्त है।

module Cash 
    def Cash.new(*args, &block) 
    env = ENV['CASH_IMPL'] 
    impl = env ? Cash.const_get(env) : LocklessImpl 
    klass = defined?(JRUBY_VERSION) ? impl : ::Hash 
    klass.new(*args) 
    end 

    class LocklessImpl 
    def initialize 
     @hash = {} 
    end 

    def thread_hash 
     thread = Thread.current 
     thread[:cash] ||= {} 
     hash = thread[:cash][thread_key] 
     if hash 
     hash 
     else 
     hash = thread[:cash][thread_key] = {} 
     ObjectSpace.define_finalizer(self){ thread[:cash].delete(thread_key) } 
     hash 
     end 
    end 

    def thread_key 
     [Thread.current.object_id, object_id] 
    end 

    def []=(key, val) 
     time = Time.now.to_f 
     tuple = [time, val] 
     @hash[key] = tuple 
     thread_hash[key] = tuple 
     val 
    end 

    def [](key) 
    # check the master value 
    # 
     val = @hash[key] 

    # someone else is either writing the key or it has never been set. we 
    # need to invalidate our own copy in either case 
    # 
     if val.nil? 
     thread_val = thread_hash.delete(key) 
     return(thread_val ? thread_val.last : nil) 
     end 

    # check our own thread local value 
    # 
     thread_val = thread_hash[key] 

    # in this case someone else has written a value that we have never seen so 
    # simply return it 
    # 
     if thread_val.nil? 
     return(val.last) 
     end 

    # in this case there is a master *and* a thread local value, if the master 
    # is newer juke our own cached copy 
    # 
     if val.first > thread_val.first 
     thread_hash.delete(key) 
     return val.last 
     else 
     return thread_val.last 
     end 
    end 
    end 

    class LockingImpl < ::Hash 
    require 'sync' 

    def initialize(*args, &block) 
     super 
    ensure 
     extend Sync_m 
    end 

    def sync(*args, &block) 
     sync_synchronize(*args, &block) 
    end 

    def [](key) 
     sync(:SH){ super } 
    end 

    def []=(key, val) 
     sync(:EX){ super } 
    end 
    end 
end 



if $0 == __FILE__ 
    iteration = 0 

    loop do 
    n = 42 
    hash = Cash.new 

    threads = 
     Array.new(10) { 
     Thread.new do 
      Thread.current.abort_on_exception = true 
      n.times do |key| 
      hash[key] = key 
      raise "#{ key }=nil" if hash[key].nil? 
      end 
     end 
     } 

    threads.map{|thread| thread.join} 

    puts "THREADSAFE: #{ iteration += 1 }" 
    end 
end 
+2

क्षमा करें मुझे इसे पढ़ने के लिए वापस लेने में इतना लंबा लगा। मुझे वास्तव में यह वास्तव में पसंद है, लेकिन मैं आपके दावे से थोड़ा असहज हूं कि "यह केवल जरुबी में एक समस्या है।" यह आज भी सच हो सकता है (या ऐसा नहीं हो सकता है, एक बार मैक्रूबी और मैग्लेव जैसे कार्यान्वयन को ध्यान में रखा जाता है), लेकिन इसे शायद भविष्य में दिए गए अनुसार नहीं लिया जा सकता है। असल में, मैं आश्चर्यचकित नहीं होगा अगर रूबी 2.0 जीआईएल से पूरी तरह से बेहतर अनाज वाले ताले के पक्ष में बचने में कामयाब रहे। और यह मैग्लेव, मैक्रूबी और संभवतः रूबिनीस में एक मुद्दा होगा। –

+0

मैं इस समाधान को स्वीकार कर रहा हूं; भयानक कार्यान्वित लॉकिंग समाधान के खिलाफ कुछ बेंचमार्क देखना अच्छा लगेगा। –

+0

मैं जोश की कॉपी-एंड-स्वैप समाधान (जिसे हम रेल में उपयोग करते हैं, के खिलाफ बेंचमार्क देखना चाहते हैं, और इस समस्या पर मेरा मूल स्टैब था) –

1

यह (video, pdf) जावा में लागू लॉक-फ्री हैश तालिका के बारे में है।

spoiler: परमाणु Compare-And-Swap (CAS) संचालन का उपयोग करता है, यदि रूबी में उपलब्ध नहीं है तो आप उन्हें ताले के साथ अनुकरण कर सकते हैं। सुनिश्चित नहीं है कि क्या यह सरल लॉक-संरक्षित हैशटेबल

+0

प्रश्न विशेष रूप से कहा गया है कि जावा समाधान स्वीकार नहीं किया जाएगा। –

+4

यह कार्यान्वयन नहीं है, यह एक एल्गोरिदम प्रस्तुति है। – Javier

+0

यह प्रोसेसर कोर स्तर पर हैश टेबल को अनुकूलित करने, कैश लाइनों, कैश मिस, और कैश समेकन प्राइमेटिव से निपटने पर एक अच्छी चर्चा है। हालांकि, शुद्ध रूबी में समस्या को हल करना उपयोगी नहीं है। –

1

पर परीक्षण नहीं किया गया है, और पढ़ने के लिए अनुकूलन पर एक बेवकूफ स्टैब पर कोई लाभ नहीं होगा। यह मानता है कि ज्यादातर समय, मूल्य लॉक नहीं होगा। यदि ऐसा है, तो तंग पाश तब तक कोशिश करेगा जब तक यह नहीं है। मैंने Thread.critical को यह सुनिश्चित करने में सहायता के लिए रखा है कि पढ़े गए धागे तब तक नहीं चलेंगे जब तक कि लेखन पूरा नहीं हो जाता। सुनिश्चित नहीं है कि महत्वपूर्ण भाग की आवश्यकता है, तो यह वास्तव में इस बात पर निर्भर करता है कि आपके पढ़ने का कितना भारी मतलब है, इसलिए कुछ बेंचमार्किंग क्रम में है।

class ConcurrentHash < Hash 

    def initialize(*args) 
    @semaphore = Mutex.new 
    super 
    end 

    def []=(k,v) 
    begin 
     old_crit = Thread.critical 
     Thread.critical = true unless old_crit 
     @semaphore.synchronize { super } 
    ensure 
     Thread.critical = old_crit 
    end 
    end 

    def [](k) 
    while(true) 
     return super unless @semaphore.locked? 
    end 
    end 

end 

कुछ अन्य पढ़ने तरीकों कि @semaphore ताला जाँच करने की आवश्यकता होगी हो सकता है, मैं अगर सब कुछ # की [] मामले में कार्यान्वित किया जाता है पता नहीं है।

+0

मुझे लगता है कि इसमें जेआरबी के तहत दौड़ की स्थिति होगी। – finnw

+0

आपका पाठक सुरक्षित नहीं है, क्योंकि सुपर परमाणु नहीं है। हमें यह सुनिश्चित करने की ज़रूरत है कि लोग पढ़ रहे हों, तब कोई लिखना शुरू न हो। –

9

पोस्टिंग आधार/अनुभवहीन समाधान, बस मेरी स्टैक ओवरफ़्लो cred बढ़ावा देने के लिए:

require 'thread' 

class ConcurrentHash < Hash 
    def initialize 
    super 
    @mutex = Mutex.new 
    end 

    def [](*args) 
    @mutex.synchronize { super } 
    end 

    def []=(*args) 
    @mutex.synchronize { super } 
    end 
end 
+1

तथ्य यह है कि आपने पोस्ट किया है * बस * प्रतिष्ठा पाने के लिए मुझे अपवित्र करने के लिए अनिच्छुक बनाता है। : पी –

+5

क्या यह गाजर को लटकाने की प्रतिष्ठा का लक्ष्य नहीं है जो उपयोगकर्ताओं को अच्छे उत्तर देने के लिए मजबूर करता है? – jshen

+3

क्या आप कम से कम, हैश पर सभी उत्परिवर्ती संचालनों को म्यूटक्स नहीं करना चाहते हैं? –

7

Yehuda, मुझे लगता है कि आप का उल्लेख इवर सेटिंग परमाणु था? तब एक साधारण प्रतिलिपि और स्वैप के बारे में क्या?

require 'thread' 

class ConcurrentHash 
    def initialize 
    @reader, @writer = {}, {} 
    @lock = Mutex.new 
    end 

    def [](key) 
    @reader[key] 
    end 

    def []=(key, value) 
    @lock.synchronize { 
     @writer[key] = value 
     @reader, @writer = @writer, @reader 
     @writer[key] = value 
    } 
    end 
end 
+0

क्या होगा यदि दो लिखने के संचालन एक ही समय में हैश लिखते हैं या तो लिखते हैं? क्या लिखने में से कोई भी खो जाएगा? –

+0

अपडेट किया गया। यह वास्तव में बहुत ज्यादा चोट नहीं पहुंचाया जाना चाहिए। – josh

+0

डुप्लिकेट से बेहतर क्लोन नहीं होगा? आईआईआरसी अंतर यह है कि क्लोन आपको एक जमे हुए वस्तु देता है यदि मूल जमे हुए था - जो आप चाहते हैं। – finnw

1

मैं इस बारे में बहुत अस्पष्ट हूं कि इसका क्या अर्थ है। मुझे लगता है कि सबसे सरल कार्यान्वयन बस है

Hash 

निर्मित गहरे लाल रंग का हैश है threadsafe कहने के लिए है, तो threadsafe से तुम्हारा मतलब उड़ा नहीं होगा> 1 धागे इसे उपयोग करने की कोशिश करता है, तो यह है कि। इस कोड को चला जाएगा सुरक्षित रूप से हमेशा के लिए

n = 4242 
hash = {} 

loop do 
    a = 
    Thread.new do 
     n.times do 
     hash[:key] = :val 
     end 
    end 

    b = 
    Thread.new do 
     n.times do 
     hash.delete(:key) 
     end 
    end 

    c = 
    Thread.new do 
     n.times do 
     val = hash[:key] 
     raise val.inspect unless [nil, :val].include?(val) 
     end 
    end 

    a.join 
    b.join 
    c.join 
    p :THREADSAFE 
end 

मैं सुरक्षित धागा द्वारा संदेह है कि आप वास्तव में एसिड मतलब - उदाहरण के लिए की तरह हैश एक लिखने [: कुंजी] =: वैल पढ़ने अगर है, जिसके बाद [: कुंजी] वापस होगा: वैल । लेकिन लॉकिंग के साथ कोई भी चालबाजी नहीं कर सकती है - आखिरी में हमेशा जीत होगी। उदाहरण के लिए, कहें कि आपके पास 42 थ्रेड है जो सभी थ्रेडसेफ हैश अपडेट कर रहा है - कौन सा मान 43'rd द्वारा पढ़ा जाना चाहिए ?? निश्चित रूप से threasafe द्वारा आप लिखने पर कुल क्रम का कोई मतलब नहीं है - इसलिए यदि 42 धागे सक्रिय रूप से 'सही' मान लिख रहे थे कोई सही है? लेकिन माणिक के अंतर्निहित सिर्फ इस तरह से काम करता है हैश ...

शायद आप की तरह

hash.each do ... 
एक सूत्र में

और

hash.delete(key) 

कुछ एक दूसरे के साथ हस्तक्षेप नहीं करेंगे क्या मतलब है?मैं चाहता है कि threadsafe होने की कल्पना कर सकते हैं, लेकिन यह है कि एमआरआई रूबी के साथ एक एकल सूत्र में भी सुरक्षित नहीं है

तो

(जाहिर है आप एक हैश इस पर पुनरावृत्ति करते हुए संशोधित नहीं कर सकते) आप तुम क्या मतलब के बारे में अधिक विशिष्ट हो सकता है 'थ्रेडसेफ' द्वारा ??

एसीआईडी ​​semantics देने का एकमात्र तरीका एक सकल ताला होगा (यकीन है कि यह एक विधि हो सकती है जो एक ब्लॉक ले लिया - लेकिन अभी भी एक बाहरी ताला)।

रूबी का थ्रेड शेड्यूलर कुछ मनमानी सी फ़ंक्शन (जैसे अंतर्निहित हैश एएसएफ एसेट विधियों) के बीच में थ्रेड स्मैक शेड्यूल करने वाला नहीं है, इसलिए वे प्रभावी रूप से थ्रेडसेफ हैं।

+0

कम से कम, मुझे आवश्यकता है कि यदि दो धागे एक ही समय में एक कुंजी सेट करने का प्रयास करते हैं, तो दोनों सेट होते हैं, और यदि एकाधिक धागे एक ही कुंजी के अनुक्रम में पढ़ते हैं + पढ़ते हैं, तो केवल दो संभावित पठन मूल्य जो सेट हैं (यानी कोई शून्य नहीं)। मैं एमआरआई व्यवहार का पालन करने के लिए सामग्री नहीं हूं, क्योंकि मैं सचमुच पढ़ने और/या लिखने जैसे मामलों में समाधान करना चाहता हूं, जैसे जेआरबी में। –

+1

मुझे यह कहना होगा कि यह जर्बी में एक बग जैसा लगता है कि हैश लिखता है और पढ़ता है परमाणु नहीं है - लेकिन वास्तव में यह मामला है। मैं बताउंगा कि रूबी हैश आपको 1.8 और 1.9 में मुफ्त में देता है, इसलिए वास्तव में आप एक कारखाना चाहते हैं जो कि जब तक आप जर्बी में न हों तब तक हैश को वापस कर दें। –

+1

मुझे विश्वास नहीं है कि "थ्रेड एक साथ काम नहीं करते हैं" रूबी की एक विशेषता माना जा सकता है। मैं एक कार्यान्वयन विस्तार के लिए एमआरआई (इसके 10 एमएमएस टाइम्सलाइस के साथ) में थ्रेड शेड्यूलर पर विचार करता हूं - और मैं चाहता हूं कि एमआरआई और अन्य कार्यान्वयन दोनों थ्रेडिंग सेमेन्टिक्स को बेहतर बनाने के लिए मुक्त हों, ऐसे सुधारों को बिना किसी बग के माना जा रहा है। –

-1

चूंकि आप उल्लेख करते हैं कि हैश को भारी पढ़ा जाएगा, जिसमें एक म्यूटेक्स लॉकिंग दोनों पढ़ने और लिखने के परिणामस्वरूप रेस की स्थिति होती है जो शायद पढ़कर जीती जाती हैं। यदि यह आपके साथ ठीक है, तो उत्तर को अनदेखा करें।

यदि आप प्राथमिकता लिखना चाहते हैं, तो एक पठन-लेखन लॉक मदद करेगा। निम्नलिखित कोड ऑपरेटिंग सिस्टम क्लास के लिए कुछ पुराने सी ++ असाइनमेंट पर आधारित है, इसलिए शायद सर्वोत्तम गुणवत्ता न हो, लेकिन एक सामान्य विचार देता है।

require 'thread' 

class ReadWriteLock 
    def initialize 
    @critical_section = Mutex.new 
    @are_writers_finished = ConditionVariable.new 
    @are_readers_finished = ConditionVariable.new 
    @readers = 0 
    @writers = 0 
    @writer_locked = false 
    end 

    def read 
    begin 
     start_read 
     yield 
    ensure 
     end_read 
    end 
    end 

    def start_read 
    @critical_section.lock 
    while (@writers != 0 || @writer_locked) 
     @are_writers_finished.wait(@critical_section) 
    end 
    @readers += 1 
    @critical_section.unlock 
    end 

    def end_read 
    @critical_section.lock 
    if (@readers -= 1) == 0 
     @are_readers_finished.broadcast 
    end 
    @critical_section.unlock 
    end 

    def write 
    begin 
     start_write 
     yield 
    ensure 
     end_write 
    end 
    end 

    def start_write 
    @critical_section.lock 
    @writers += 1 
    while @readers > 0 
     @are_readers_finished.wait(@critical_section) 
    end 
    while @writer_locked 
     @are_writers_finished.wait(@critical_section) 
    end 
    @writers -= 1 
    @writer_locked = true 
    @critical_section.unlock 
    end 

    def end_write 
    @critical_section.lock 
    @writer_locked = false 
    @are_writers_finished.broadcast 
    @critical_section.unlock 
    end 
end 

फिर बस [] = और [] को lock.write और lock.read में लपेटें। शायद एक प्रदर्शन प्रभाव हो सकता है, लेकिन यह गारंटी देगा कि लिखने से पढ़ने के माध्यम से 'मिल जाएगा'। इसका उपयोग इस बात पर निर्भर करता है कि वास्तव में यह कितना भारी है।

+0

गलत शामिल है। एक एकल म्यूटेक्स प्रत्येक पाठक या लेखक को समान प्राथमिकता देता है। –

4

यह हैश के चारों ओर एक रैपर वर्ग है जो समवर्ती पाठकों को अनुमति देता है, लेकिन अन्य सभी प्रकार की पहुंच (पुनरावृत्त समेत) के लिए चीज़ों को बंद कर देता है।

class LockedHash 
    def initialize 
    @hash = Hash.new 
    @lock = ThreadAwareLock.new() 
    @reader_count = 0 
    end 

    def [](key) 
    @lock.lock_read 
    ret = @hash[key] 
    @lock.unlock_read 
    ret 
    end 

    def []=(key, value) 
    @lock.lock_write 
    @hash[key] = value 
    @lock.unlock_write 
    end 

    def method_missing(method_sym, *arguments, &block) 
    if @hash.respond_to? method_sym 
     @lock.lock_block 
     val = lambda{@hash.send(method_sym,*arguments, &block)}.call 
     @lock.unlock_block 
     return val 
    end 
    super 
    end 
end 

यहाँ ताला कोड का उपयोग करता है है:

class RWLock 
    def initialize 
    @outer = Mutex.new 
    @inner = Mutex.new 
    @reader_count = 0 
    end 
    def lock_read 
    @outer.synchronize{@inner.synchronize{@reader_count += 1}} 
    end 
    def unlock_read 
    @inner.synchronize{@reader_count -= 1} 
    end 
    def lock_write 
    @outer.lock 
    while @reader_count > 0 ;end 
    end 
    def unlock_write 
    @outer.unlock 
    end 
end 

class ThreadAwareLock < RWLock 
    def initialize 
    @owner = nil 
    super 
    end 
    def lock_block 
    lock_write 
    @owner = Thread.current.object_id 
    end 
    def unlock_block 
    @owner = nil 
    unlock_write 
    end 
    def lock_read 
    super unless my_block? 
    end 
    def unlock_read 
    super unless my_block? 
    end 
    def lock_write 
    super unless my_block? 
    end 
    def unlock_write 
    super unless my_block? 
    end 
    def my_block? 
    @owner == Thread.current.object_id 
    end 
end 

धागा अवगत ताला आप कक्षा एक बार लॉक करने के लिए अनुमति देने के लिए, और फिर तरीकों कि सामान्य रूप से लॉक होता कहते हैं, और उन्हें नहीं है ताला। आपको इसकी आवश्यकता है क्योंकि आप कुछ तरीकों के अंदर ब्लॉक में आते हैं, और वे ब्लॉक ऑब्जेक्ट पर लॉकिंग विधियों को कॉल कर सकते हैं, और आप डेडलॉक या डबल-लॉक त्रुटि नहीं चाहते हैं। इसके बजाय आप एक गिनती लॉक का उपयोग कर सकते हैं।

यहाँ बाल्टी स्तर के पढ़ने-लिखने ताले को लागू करने का प्रयास है:

class SafeBucket 
    def initialize 
    @lock = RWLock.new() 
    @value_pairs = [] 
    end 

    def get(key) 
    @lock.lock_read 
    pair = @value_pairs.select{|p| p[0] == key} 
    unless pair && pair.size > 0 
     @lock.unlock_read 
     return nil 
    end 
    ret = pair[0][1] 
    @lock.unlock_read 
    ret 
    end 

    def set(key, value) 
    @lock.lock_write 
    pair = @value_pairs.select{|p| p[0] == key} 
    if pair && pair.size > 0 
     pair[0][1] = value 
     @lock.unlock_write 
     return 
    end 
    @value_pairs.push [key, value] 
    @lock.unlock_write 
    value 
    end 

    def each 
    @value_pairs.each{|p| yield p[0],p[1]} 
    end 

end 

class MikeConcurrentHash 
    def initialize 
    @buckets = [] 
    100.times {@buckets.push SafeBucket.new} 
    end 

    def [](key) 
    bucket(key).get(key) 
    end 

    def []=(key, value) 
    bucket(key).set(key, value) 
    end 

    def each 
    @buckets.each{|b| b.each{|key, value| yield key, value}} 
    end 

    def bucket(key) 
    @buckets[key.hash % 100] 
    end 
end 

मैं इस क्योंकि यह बहुत धीमी है पर काम करना बंद कर, इसलिए प्रत्येक विधि (एक यात्रा के दौरान अन्य धागे से म्यूटेशन की अनुमति देता है) असुरक्षित है और यह अधिकांश हैश विधियों का समर्थन नहीं करता है।

और यहाँ समवर्ती हैश के लिए एक परीक्षण दोहन है:

require 'thread' 
class HashHarness 
    Keys = [:a, :basic, :test, :harness, :for, :concurrent, :testing, :of, :hashes, 
      :that, :tries, :to, :provide, :a, :framework, :for, :designing, :a, :good, :ConcurrentHash, 
      :for, :all, :ruby, :implementations] 

    def self.go 
    h = new 
    r = h.writiness_range(20, 10000, 0, 0) 
    r.each{|k, v| p k + ' ' + v.map{|p| p[1]}.join(' ')} 
    return 
    end 
    def initialize(classes = [MikeConcurrentHash, JoshConcurrentHash, JoshConcurrentHash2, PaulConcurrentHash, LockedHash, Hash]) 
    @classes = classes 
    end 
    def writiness_range(basic_threads, ops, each_threads, loops) 
    result = {} 
    @classes.each do |hash_class| 
     res = [] 
     0.upto 10 do |i| 
     writiness = i.to_f/10 
     res.push [writiness,test_one(hash_class, basic_threads, ops, each_threads, loops, writiness)] 
     end 
     result[hash_class.name] = res 
    end 
    result 
    end 
    def test_one(hash_class, basic_threads, ops, each_threads, loops, writiness) 
    time = Time.now 
    threads = [] 
    hash = hash_class.new 
    populate_hash(hash) 
    begin 
    basic_threads.times do 
     threads.push Thread.new{run_basic_test(hash, writiness, ops)} 
    end 
    each_threads.times do 
     threads.push Thread.new{run_each_test(hash, writiness, loops)} 
    end 
    threads.each{|t| t.join} 
    rescue ThreadError => e 
     p [e.message, hash_class.name, basic_threads, ops, each_threads, loops, writiness].join(' ') 
     return -1 
    end 
    p [hash_class.name, basic_threads, ops, each_threads, loops, writiness, Time.now - time].join(' ') 
    return Time.now - time 
    end 
    def run_basic_test(hash, writiness, ops) 
    ops.times do 
     rand < writiness ? hash[choose_key]= rand : hash[choose_key] 
    end 
    end 
    def run_each_test(hash, writiness, loops) 
    loops.times do 
     hash.each do |k, v| 
     if rand < writiness 
      each_write_work(hash, k, v) 
     else 
      each_read_work(k, v) 
     end 
     end 
    end 
    end 
    def each_write_work(hash, key, value) 
    hash[key] = rand 
    end 
    def each_read_work(key, value) 
    key.to_s + ": " + value.to_s 
    end 
    def choose_key 
    Keys[rand(Keys.size)] 
    end 
    def populate_hash(hash) 
    Keys.each{|key| hash[key]=rand} 
    end 
end 

नंबर: JRuby

Writiness  0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 
ConcurrentHash 2.098 3.179 2.971 3.083 2.731 2.941 2.564 2.480 2.369 1.862 1.881 
LockedHash  1.873 1.896 2.085 2.058 2.001 2.055 1.904 1.921 1.873 1.841 1.630 
Hash   0.530 0.672 0.685 0.822 0.719 0.877 0.901 0.931 0.942 0.950 1.001 

और एमआरआई

Writiness  0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 
ConcurrentHash 9.214 9.913 9.064 10.112 10.240 10.574 10.566 11.027 11.323 11.837 13.036 
LockedHash  19.593 17.712 16.998 17.045 16.687 16.609 16.647 15.307 14.464 13.931 14.146 
Hash   0.535 0.537 0.534 0.599 0.594 0.676 0.635 0.650 0.654 0.661 0.692 

एमआरआई संख्या बहुत हड़ताली है। एमआरआई में लॉकिंग वास्तव में बेकार है।

+1

अच्छा। हां, मैं हर हैश विधि का समर्थन करने के लिए समाधान चाहता हूं। मुझे चिंता है कि शुद्ध-रूबी में बाल्टी सेमेन्टिक्स को लागू करना वास्तव में बुलेट को काटने और हर बार लॉकिंग के पर्फ हिट लेने से धीमा होगा। इसे बेंच करने की देखभाल? –

2

यह hamster gem

हैम्स्टर के लिए एक उपयोग के मामले हो सकता है Hash Array Mapped Tries (HAMT), साथ ही कुछ अन्य persistent data structures लागू करता है, शुद्ध रूबी में।

लगातार डेटा संरचनाएं अपरिवर्तनीय हैं, और संरचना को बदलने (बदलने) की बजाय, जैसे कि हैश में एक कुंजी-मूल्य जोड़ी जोड़कर या बदलकर, आप बदले में एक नई डेटा संरचना लौटाते हैं जिसमें परिवर्तन होता है। लगातार अपरिवर्तनीय डेटा संरचनाओं के साथ चाल यह है कि नई लौटाई गई डेटा संरचना यथासंभव अधिक से अधिक पूर्ववर्ती का पुन: उपयोग करती है।

मुझे लगता है कि हम्सटर का उपयोग करके लागू करने के लिए, आप अपने उत्परिवर्ती हैश रैपर का उपयोग करेंगे, जो सभी निरंतर अपरिवर्तनीय हैश (यानी, तेज़ होना चाहिए) के वर्तमान मूल्य को पढ़ता है, जबकि सभी को म्यूटेक्स के साथ लिखते हैं, और स्वैपिंग लिखने के बाद लगातार अपरिवर्तनीय हैश के नए मूल्य के लिए।

उदाहरण के लिए: निम्नलिखित

(gist here)

require 'hamster' 
require 'hamster/experimental/mutable_hash' 

# a bunch of threads with a read/write ratio of 10:1 
num_threads = 100 
num_reads_per_write = 10 
num_loops = 100 
hsh = Hamster.mutable_hash 

puts RUBY_DESCRIPTION 
puts "#{num_threads} threads x #{num_loops} loops, #{num_reads_per_write}:1 R/W ratio" 

t0 = Time.now 
Thread.abort_on_exception = true 
threads = (0...num_threads).map do |n| 
    Thread.new do 
    write_key = n % num_reads_per_write 
    read_keys = (0...num_reads_per_write).to_a.shuffle # random order 
    last_read = nil 

    num_loops.times do 
     read_keys.each do |k| 
     # Reads 
     last_read = hsh[k] 

     Thread.pass 

     # Atomic increments in the correct ratio to reads 
     hsh.put(k) { |v| (v || 0) + 1 } if k == write_key 
     end 
    end 
    end 
end 

threads.map { |t| t.join } 
t1 = Time.now 

puts "Error in keys" unless (0...num_reads_per_write).to_a == hsh.keys.sort.to_a 
puts "Error in values" unless hsh.values.all? { |v| v == (num_loops * num_threads)/num_reads_per_write } 
puts "Time elapsed: #{t1 - t0} s" 

मैं हो रही है:

require 'hamster' 
require 'hamster/experimental/mutable_hash'  
hsh = Hamster.mutable_hash(:name => "Simon", :gender => :male) 

# reading goes directly to hash 
puts hsh[:name] # Simon 

# writing is actually swapping to new value of underlying persistent data structure 
hsh.put(:name, "Joe") 
puts hsh[:name] # Joe 

तो चलिए वर्णित एक करने के लिए समस्या का एक समान प्रकार के लिए इस का उपयोग करते हैं आउटपुट:

ruby 1.9.2p320 (2012-04-20 revision 35421) [x86_64-linux] 
100 threads x 100 loops, 10:1 R/W ratio 
Time elapsed: 5.763414627 s 

jruby 1.7.0 (1.9.3p203) 2012-10-22 ff1ebbe on Java HotSpot(TM) 64-Bit Server VM 1.6.0_26-b03 [linux-amd64] 
100 threads x 100 loops, 10:1 R/W ratio 
Time elapsed: 1.697 s 

आप इस बारे में क्या सोचते हैं?

यह समाधान स्कैला या क्लोजर में इसे हल करने के तरीके के समान ही है, हालांकि उन भाषाओं में एक परमाणु तुलना और स्वैप संचालन के लिए निम्न स्तर के सीपीयू समर्थन के साथ सॉफ़्टवेयर ट्रांजैक्शनल मेमोरी का उपयोग करने की संभावना अधिक होगी।

संपादित करें: यह ध्यान देने योग्य है कि हम्सटर कार्यान्वयन तेज़ होने का एक कारण यह है कि इसमें lock-free read path है। अगर आपके पास इसके बारे में कोई सवाल है या यह कैसे काम करता है, तो कृपया टिप्पणियों में जवाब दें। वर्ग RWLock और साथ @reader_count आदि (अभी तक पर्याप्त कर्म नहीं है)

कि समाधान काम नहीं करता वर्ग LockedHash:

0

दुर्भाग्य से मैं नहीं एक टिप्पणी माइकल Sofaer जवाब जहां उन्होंने परिचय करने के लिए जोड़ सकते हैं। यह एक त्रुटि देता है: 'अनलॉक' में: एक म्यूटेक्स को अनलॉक करने का प्रयास करें जो लॉक नहीं है (थ्रेडरर)

तार्किक बग के कारण: जब चीजों को अनलॉक करने का समय होता है तो अनलॉक होता है 1 अतिरिक्त समय (अनुपलब्ध चेक के कारण) my_block?()। इसके बजाए यह अनब्लॉक करने की आवश्यकता नहीं है, भले ही अनब्लॉकिंग आवश्यक न हो "मेरा ब्लॉक है") और इसलिए पहले से अनलॉक म्यूट्स पर दूसरा अनलॉक अपवाद उठाता है।(मैं इस पोस्ट के अंत में इस त्रुटि को पुन: उत्पन्न करने के तरीके पर पूर्ण कोड पेस्ट करूंगा)।

इसके अलावा माइकल ने उल्लेख किया कि "प्रत्येक विधि असुरक्षित है (एक पुनरावृत्ति के दौरान अन्य धागे द्वारा उत्परिवर्तन की अनुमति देता है)" जो मेरे लिए महत्वपूर्ण था, इसलिए मैं इस सरलीकृत समाधान के साथ समाप्त होता हूं जो मेरे सभी उपयोग मामलों के लिए काम करता है और यह बस म्यूटेक्स को ताला लगा देता है प्रदर्शित/की त्रुटि पुन: पेश करने

# 
# This TrulyThreadSafeHash works! 
# 
# Note if one thread iterating the hash by #each method 
# then the hash will be locked for all other threads (they will not be 
# able to even read from it) 
# 
class TrulyThreadSafeHash 
    def initialize 
    @mutex = Mutex.new 
    @hash = Hash.new 
    end 

    def method_missing(method_sym, *arguments, &block) 

    if [email protected]? # Returns true if this lock is currently held by current thread 
     # We're trying to lock only if mutex is not owned by the current thread (is not locked or is locked by some other thread). 
     # Following call will be blocking if mutex locked by other thread: 
     @mutex.synchronize{ 
      return lambda{@hash.send(method_sym,*arguments, &block)}.call 
     } 
    end 

    # We already own the lock (from current thread perspective). 
    # We don't even check if @hash.respond_to?(method_sym), let's make Hash 
    # respond properly on all calls (including bad calls (example: wrong method names)) 
    lambda{@hash.send(method_sym,*arguments, &block)}.call 
    end 

    # since we're tyring to mimic Hash we'll pretend to respond as Hash would 
    def self.respond_to?(method_sym, include_private = false) 
    Hash.respond_to(method_sym, include_private) 
    end 

    # override Object's to_s because our method_missing won't be called for to_s 
    def to_s(*arguments) 
     @mutex.synchronize{ 
     return @hash.to_s 
     } 
    end 

    # And for those, who want to run extra mile: 
    # to make our class json-friendly we shoud require 'json' and uncomment this: 
    #def to_json(*options) 
    # @mutex.synchronize{ 
    #  return @hash.to_json(*options) 
    # } 
    #end 

end 

और अब पूर्ण उदाहरण: जब विभिन्न धागे से कहा जाता है किसी भी हैश विधि के लिए किसी भी फोन पर (एक ही धागा है, जो ताला मालिक से कॉल गतिरोध से बचने के लिए अवरुद्ध नहीं कर रहे) माइकल सोफायर के समाधान में डबल अनलॉकिंग:

#!/usr/bin/env ruby 

# ======= unchanged copy-paste part from Michael Sofaer answer (begin) ======= 

class LockedHash 
    def initialize 
    @hash = Hash.new 
    @lock = ThreadAwareLock.new() 
    @reader_count = 0 
    end 

    def [](key) 
    @lock.lock_read 
    ret = @hash[key] 
    @lock.unlock_read 
    ret 
    end 

    def []=(key, value) 
    @lock.lock_write 
    @hash[key] = value 
    @lock.unlock_write 
    end 

    def method_missing(method_sym, *arguments, &block) 
    if @hash.respond_to? method_sym 
     @lock.lock_block 
     val = lambda{@hash.send(method_sym,*arguments, &block)}.call 
     @lock.unlock_block 
     return val 
    end 
    super 
    end 
end 



class RWLock 
    def initialize 
    @outer = Mutex.new 
    @inner = Mutex.new 
    @reader_count = 0 
    end 
    def lock_read 
    @outer.synchronize{@inner.synchronize{@reader_count += 1}} 
    end 
    def unlock_read 
    @inner.synchronize{@reader_count -= 1} 
    end 
    def lock_write 
    @outer.lock 
    while @reader_count > 0 ;end 
    end 
    def unlock_write 
    @outer.unlock 
    end 
end 

class ThreadAwareLock < RWLock 
    def initialize 
    @owner = nil 
    super 
    end 
    def lock_block 
    lock_write 
    @owner = Thread.current.object_id 
    end 
    def unlock_block 
    @owner = nil 
    unlock_write 
    end 
    def lock_read 
    super unless my_block? 
    end 
    def unlock_read 
    super unless my_block? 
    end 
    def lock_write 
    super unless my_block? 
    end 
    def unlock_write 
    super unless my_block? 
    end 
    def my_block? 
    @owner == Thread.current.object_id 
    end 
end 

# ======= unchanged copy-paste part from Michael Sofaer answer (end) ======= 


# global hash object, which will be 'shared' across threads 
$h = LockedHash.new 

# hash_reader is just iterating through the 'shared' hash $h 
# and prints specified delimeter (capitalized when last hash item read) 
def hash_reader(delim) 
    loop{ 
     count = 0 
     $h.each{ 
      count += 1 
      if count != $h.size 
       $stderr.print delim 
      else 
       $stderr.puts delim.upcase 
      end 
     } 
    } 
end 

# fill hash with 10 items 
10.times{|i| 
    $h[i] = i 
} 

# create a thread which will read $h hash 
t1 = Thread.new(){ 
    hash_reader("o") 
} 

t1.join # will never happen, but for completeness 

है, जो निम्न त्रुटि देता है:

./LockedHash_fails_to_unlock.rb 
oooooooooO 
./LockedHash_fails_to_unlock.rb:55:in `unlock': Attempt to unlock a mutex which is not locked (ThreadError) 
     from ./LockedHash_fails_to_unlock.rb:55:in `unlock_write' 
     from ./LockedHash_fails_to_unlock.rb:82:in `unlock_write' 
     from ./LockedHash_fails_to_unlock.rb:70:in `unlock_block' 
     from ./LockedHash_fails_to_unlock.rb:29:in `method_missing' 
     from ./LockedHash_fails_to_unlock.rb:100:in `block in hash_reader' 
     from ./LockedHash_fails_to_unlock.rb:98:in `loop' 
     from ./LockedHash_fails_to_unlock.rb:98:in `hash_reader' 
     from ./LockedHash_fails_to_unlock.rb:119:in `block in <main>' 
संबंधित मुद्दे