Ruby thread (2)

Source: Internet
Author: User
Tags export class

6, Using a Thread Group

A thread group is a way to manage threads. It logically associates threads with each other. Generally, all threads belong to the Default thread group (it is a class constant ). If a new thread group is created, the new thread is added to the group.

A thread can belong to only one thread group at a time. When a thread is added to a thread group, it is automatically removed from its previous thread group.

ThreadGroup. new Class method creates a new thread group, and then adds the adds instance method to the group:

F1thread = Thread. new ("file1") {| file | waitfor (file )}

F2thread = Thread. new ("file2") {| file | waitfor (file )}

File_threads = ThreadGroup. new

File_threads.add f1

File_threads.add f2

# Count living threads in this_group

Count = 0

This_group.list.each {| x | count + = 1 if x. alive? }

If count <this_group.list.size

Puts "Some threads in this_group are not living ."

Else

Puts "All threads in this_group are alive ."

End

There are many useful methods to add to ThreadGroup. Here we show the method to wake up every thread in the group, wait to capture all threads (through join), and kill all threads in the group:

Class ThreadGroup

Def wakeup

List. each {| t. wakeup}

End

Def join

List. each {| t. join if t! = Thread. current}

End

Def kill

List. each {| t. kill}

End

End

II,Synchronizing Threads

Why is synchronization required? This is because operations are staggered to cause variables and other entities, which are not explicitly accessed by reading code from different threads. Two or more threads can access the same variable to affect each other, which is unpredictable and difficult to debug.

Let's take a look at the code snippet of this example:

X = 0

T1 = Thread. new do

1. upto (1000) do

X = x + 1

End

End

T2 = Thread. new do

1. upto (1000) do

X = x + 1

End

End

T1.join

T2.join

Puts x

The variable x starts with 0. Each thread adds it once every 1000 seconds. The logic tells us that x must be 2000 in output.

But what do we have here? On a specific system, It prints 1044 as the result. Where is the error?

The Code assumes that the increase operation of an integer is an atomic (or inseparable) operation. But it is not. Consider the following logical process. We place thread t1 on the left and thread t2 on the right. Each row is a separate time slice. We assume that the value of x is 123 when the logical slice is entered.

T1 thread t2 thread

____________________________________________________

Obtain the value of x (123)

Obtain the value of x (123)

Add the value to 1 (124)

Add the value to 1 (124)

Store results in x

Store the result in x.

Obviously, each thread completes simple incremental operations from its own point of view. In this case, it is also obvious that after the incremental execution of the two threads, x is only 124.

This is just a simple synchronization problem. The worst part will become more difficult to manage and become the real object of research by computer scientists and mathematicians.

1And use the critical section to complete simple synchronization.

A simple form of synchronization is to use the critical section. When a thread enters the critical section of the Code, this technology ensures that no other thread will be run until the first thread leaves its critical section.

Thread. critical accessors. When set to true, other threads are blocked from being scheduled. Let's look at an example. We only discuss and use this technology to correct it.

X = 0

T1 = Thread. new do

1. upto (1000) do

Thread. critical = true

X = x + 1

Thread. critical = false

End

End

T2 = Thread. new do

1. upto (1000) do

Thread. critical = true

X = x + 1

Thread. critical = false

End

End

T1.join

T2.join

Puts x

Now the logical flow is forced to be similar to the following. (Of course, outside the incremental part, threads are freely staggered more or less randomly .)

T1 thread t2 thread

____________________________________________________

Retrieve the value of x (123)

Incremental operations (124)

Store results back to x

Retrieve the value of x (124)

Incremental operations (125)

Store results back to x

The combination of thread management and finished operations is possible, which will cause one thread to be scheduled, even if the other thread is in the critical section. In the simplest case, the newly created thread runs immediately, regardless of whether the other thread is in the critical section. Therefore, this technology should only be used in the simplest environment.

2, Synchronous access to resources (mutex. rb)

Let's take a Web index application as an example. Words are extracted from multiple sources on the network and stored in a hash table. The word itself will be used as a key, and the value is a string that identifies the document and the line number in the document.

This is a very rough example. But for simple reasons, we make it more rough:

1. The remote document is described as a simple string.

2. We will limit it to three strings (simple hard-coded data ).

3. We use random sleep to simulate network access changes.

So let's take a look at Listing7.1. It does not even print the data it collects, and there is only one count of the number of words found. Note that whenever the hash table is checked or changed, we call the hesitate method to sleep at random intervals. This will allow the program to run in a more uncertain and realistic way.

Listing 7.1 Flawed Indexing Example (with a Race Condition)

$ List = []

$ List [0] = "shoes shipsnsealing-wax"

$ List [1] = "cabbages kings"

$ List [2] = "quarksnshipsncabbages"

 

 

Def hesitate

Sleep rand (0)

End

$ Hash = {}

Def process_list (listnum)

Lnum = 0

$ List [listnum]. each do | line |

Words = line. chomp. split

Words. each do | w |

Hesitate

If $ hash [w]

Hesitate

$ Hash [w] + = ["# {listnum }:#{ lnum}"]

Else

Hesitate

$ Hash [w] = ["# {listnum {:#{ lnum}"]

End

End

Lnum + = 1

End

End

T1 = Thread. new (0) {| list | process_list (list )}

T2 = Thread. new (1) {| list | process_list (list )}

T3 = Thread. new (2) {| list | process_list (list )}

T1.join

T2.join

T3.join

 

 

Count = 0

$ Hash. values. each do | v |

Count + = v. size

End

Puts "Total: # {count} words" # May print 7 or 8!

But there is a problem. If your system behavior is the same as ours, here are two numbers that the program may output! In our test, it prints 7 and 8 approximately equally. When there are more words, there will be more changes.

Let's try to use mutex to correct it. mutex is used to control access to shared resources. (Of course, this term comes from the word mutual exclusion .) The Mutex library allows us to create and manipulate a mutex. When we are preparing to access the hash table, we can lock it. When we are finished, we unlock it (see Listing7.2 ).

Listing 7.2 Mutex Protected Indexing Example

Require "thread. rb"

 

 

$ List = []

$ List [0] = "shoes shipsnsealing-wax"

$ List [1] = "cabbages kings"

$ List [2] = "quarksnshipsncabbages"

Def hesitate

Sleep rand (0)

End

$ Hash = {}

$ Mutex = Mutex. new

Def process_list (listnum)

Lnum = 0

$ List [listnum]. each do | line |

Words = line. chomp. split

Words. each do | w |

Hesitate

$ Mutex. lock

If $ hash [w]

Hesitate

$ Hash [w] + = ["# {listnum }:#{ lnum}"]

Else

Hesitate

$ Hash [w] = ["# {listnum {:#{ lnum}"]

End

$ Mutex. unlock

End

Lnum + = 1

End

End

T1 = Thread. new (0) {| list | process_list (list )}

T2 = Thread. new (1) {| list | process_list (list )}

T3 = Thread. new (2) {| list | process_list (list )}

T1.join

T2.join

T3.join

Count = 0

$ Hash. values. each do | v |

Count + = v. size

End

Puts "Total: # {count} words" # Always prints 8!

In addition to lock, the Mutex class also has the try_lock method. Its behavior is similar to lock, except when another thread is locked, it will directly return false without waiting.

$ Mutex = Mutex. new

T1 = Thread. new {$ mutex. lock; sleep 30}

 

 

Sleep 1

T2 = Thread. new do

If $ mutex. try_lock

Puts "Locked it"

Else

Puts "cocould not lock" # Prints immediately

End

End

Sleep 2

This feature is useful whenever a thread does not want to be locked.

 

 

3And use the predefined synchronized Queue.Class

Thread. rb has several sometimes useful classes. Queue is a thread-sensitive Queue at the end of the synchronous access Queue. That is to say, different threads can use the same Queue without any problems. The SizedQueue class is essentially the same, except that it allows to limit the queue size (the number of elements that a queue can contain ).

There are many similar methods, because SizedQueue actually inherits Queue. The export class also has the accessor max to use or set the maximum size of the queue.

Buff = SizedQueue. new (25)

Upper1 = buff. max #25

# Now raise it...

Buff. max = 50

Upper2 = buff. max #50

Listing7.3 shows a simple producer-consumer demo. Consumers are displayed at an average time (through a long sleep time) for collection of entries.

Listing 7.3 The Producer-Consumer Problem

Require "thread"

 

 

Buffer = SizedQueue. new (2)

Producer = Thread. new do

Item = 0

Loop do

Sleep rand 0

Puts "Producer makes # {item }"

Buffer. enq item

Item + = 1

End

End

 

 

Consumer = Thread. new do

Loop do

Sleep (rand 0) + 1, 0.9

Item = buffer. deq

Puts "Consumer retrieves # {item }"

Puts "waiting = # {buffer. num_waiting }"

End

End

Sleep 60 # Run a minute, then die and kill threads

Methods enq and deq are recommended for obtaining inbound and outbound entries of a queue. We can also use push to add entries to the queue and use pop or shift to Remove entries from the queue. However, when we explicitly use the queue, these names are somewhat less valuable to memory.

Method empty? Test the empty queue. The clear method clears the queue. The size (alias length) method returns the actual number of entries in the queue.

# Assume no other threads interfering...

Buff = Queue. new

Buff. enq "one"

Buff. enq "two"

Buff. enq "three"

N1 = buff. size #3

Flag1 = buff. empty? # False

Buff. clear

N2 = buff. size #0

Flag2 = buff. empty? # True

The num_waiting method is the number of threads waiting for access to the queue. In a queue with no specified size, it is the number of threads waiting to remove elements. In a queue with a specified size, it is also the number of threads waiting to add elements to the queue.

The deq method in the Queue class has the optional parameter non_block. The default value is false. If it is true, an empty queue will produce a ThreadError instead of locking the thread.

 

 

4Use conditional variables

And he called for his fiddlers three.

"Old King Cole" (traditional folk tune)

A condition variable is a real thread queue. It is used together with mutex to provide high-level Control During thread synchronization.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.