Exploring the multi-thread mechanism of c # (I)

Source: Internet
Author: User
I. multithreading Concept

Windows is a multitasking system. If you are using Windows 2000 or later, you can view Program And process. What is a process? When a program starts to run, it is a process that refers to the memory and system resources used by the running programs and programs. A process is composed of multiple threads. A thread is an execution stream in the program. Each thread has its own proprietary register (Stack pointer, program counter, etc.). However Code Zones are shared, that is, different threads can execute the same function. Multithreading means that a program contains multiple execution streams, that is, a program can run multiple different threads to execute different tasks at the same time, that is to say, a single program is allowed to create multiple parallel threads to complete their respective tasks. A browser is a good example of multithreading. In a browser, you can scroll the page while downloading a Java application or image, and play animations and sounds when accessing a new page, print files.

The advantage of Multithreading is that it can improve the CPU utilization. Any programmer does not want his or her program to work normally. In a multithreaded program, a thread must wait, the CPU can run other threads instead of waiting, which greatly improves the program efficiency.

However, we must also recognize the negative aspects that the thread itself may affect system performance to use the thread correctly:

    • The thread is also a program, so the thread needs to occupy the memory. The more threads occupy the memory, the more
    • Multithreading requires coordination and management, so it requires CPU time to track threads.
    • Inter-thread access to shared resources will affect each other, and the problem of competing to share resources must be solved.
    • Too many threads will lead to complicated control, and may eventually cause many bugs.

Based on the above understanding, we can use a metaphor to deepen our understanding. Suppose there is a company with many employees with their respective duties, then we can think that this normal operating company is a process, and the staff in the company is a thread. A company must have at least one employee. Similarly, a process must contain at least one thread. In a company, you can do everything for one employee, but the efficiency is obviously not high. A company cannot do anything bigger. In a program, you can use only one thread to do things, in fact, this is true for outdated languages such as fortune and basic, but like a company, it is very inefficient. If it is a large program, it is less efficient-in fact, there is almost no single-threaded commercial software. The more employees the company has, the more salaries the boss has to give to them, and it has to spend a lot of energy to manage them and coordinate the contradictions and interests between them. The same is true for the program, the more threads consume more resources, the CPU time is required to track the threads, and problems such as deadlocks and synchronization must be solved. In short, if you don't want your company to be called a "bag company", you will have a few more employees. If you don't want your program to look childish, introduce multithreading in your program!

This article will discuss the multithreading mechanism in C # programming and solve the thread control and multi-thread communication issues through some examples. In order to save the tedious steps for creating a GUI and more clearly approach the essence of the thread, all the following programs are console programs and the final console of the program. readline () is used to stop the program midway through, so that you can see the output in the execution process clearly.

Okay, let's try out the multi-threaded C!

2. manipulate a thread

When any program is executed, there must be at least one main thread. The following small program can give readers an intuitive impression:

// Systemthread. CS
Using system;
Using system. Threading;

Namespace threadtest
{
Class Runit
{
[Stathread]
Static void main (string [] ARGs)
{
Thread. currentthread. Name = "system thread"; // name the current thread "system thread"
Console. writeline (thread. currentthread. Name + "'status:" + thread. currentthread. threadstate );
Console. Readline ();
}
}
}

What do you see after compilation and execution? Yes, the program will generate the following output:

System thread's status: Running

Here, we get the thread currently being executed through the static attribute currentthread of the thread class, assign the value "system thread" to its name attribute, and finally output its current state (threadstate ). The so-called static attribute is the public attribute of all objects in the class. No matter how many instances of the class you have created, the static attribute of the class has only one in the memory. It is easy to understand why currentthread is static-although multiple threads exist at the same time, the CPU can only execute one of them at a certain time point.

As demonstrated by the above program, we use the Thread class to create and control threads. Note that the program header uses the following namespace:

Using system;
Using system. Threading;


In. NET Framework class library, all classes related to multithreading applications are stored in the system. Threading namespace. The thread class is used to create threads, and the threadpool class is used to manage thread pools. In addition, it provides a mechanism to solve actual problems such as thread execution arrangements, deadlocks, and inter-thread communication. If you want to use multithreading in your application, you must include this class. The thread class has several important methods, which are described as follows:

    • Start (): Start the thread
    • Sleep (INT): a static method that pauses the specified number of milliseconds of the current thread.
    • Abort (): This method is usually used to terminate a thread.
    • Suspend (): This method does not terminate the unfinished thread. It only suspends the thread and can be recovered later.
    • Resume (): resume the execution of threads suspended by the suspend () method

Next we will create a thread. When using the Thread class to create a thread, we only need to provide the thread entry. The thread entry tells the program what to do with this thread. in C #, the thread entry is provided through the threadstart proxy (delegate). You can regard threadstart as a function pointer, point to the function to be executed by the thread. after the start () method, the thread starts to execute the Functions Represented or pointed to by threadstart.

Open your vs.net and create a console application. The following code will give you the pleasure of fully controlling a thread!

// Threadtest. CS

Using system;
Using system. Threading;

Namespace threadtest
{
Public Class Alpha
{
Public void beta ()
{
While (true)
{
Console. writeline ("Alpha. Beta is running in its own thread .");
}
}
};

Public class simple
{
Public static int main ()
{
Console. writeline ("thread start/stop/join sample ");

alpha oalpha = new alpha ();
file: // create a thread to execute beta () of the Alpha Class () method
thread othread = new thread (New threadstart (oalpha. beta);
othread. start ();
while (! Othread. isalive);
thread. sleep (1);
othread. abort ();
othread. join ();
console. writeline ();
console. writeline ("Alpha. beta has finished ");
try
{< br> console. writeline ("try to restart the Alpha. beta thread ");
othread. start ();
}< br> catch (threadstateexception)
{< br> console. write ("threadstateexception trying to restart Alpha. beta. ");
console. writeline ("expected since aborted threads cannot be restarted. ");
console. readline ();
}< br> return 0;
}< BR >}

This program contains two classes: Alpha and simple. when creating the thread othread, we use the pointer to Alpha. the threadstart proxy (delegate) object is initialized in the beta () method. When the created thread othread calls othread. when the START () method is started, the actual program running is Alpha. beta () method:

Alpha oalpha = new alpha ();
Thread othread = new thread (New threadstart (oalpha. Beta ));
Othread. Start ();

Then in the while loop of the main () function, we use the static method thread. Sleep () to stop the main thread for 1 ms. During this time, the CPU turns to the execution thread othread. Then we try to use the thread. Abort () method to terminate the thread othread. Pay attention to the following othread. Join (), thread. Join () method to wait for the main thread until the othread thread ends. You can specify an int-type parameter for the thread. Join () method as the maximum waiting time. Later, we tried to use the thread. Start () method to restart the thread othread, but obviously the consequence of the abort () method is that the thread cannot be recovered, so the program will throw a threadstateexception.

The final result of the program will be as follows:

Note that other threads are attached to the thread where the main () function is located. The main () function is the entry of the C # program. The starting thread can be called the main thread, if all foreground threads are stopped, the main thread can be terminated, and all background threads will be terminated unconditionally. While all threads are executed in a serial way at the micro level, you can think of them as being executed in parallel at the macro level.

The reader must have noticed the thread. threadstate, which represents the state of a thread during runtime and has different values under different circumstances. Therefore, we can design a program flow by judging the value. Threadstate may take the following values in various situations:

    • Aborted: the thread has stopped.
    • Abortrequested: The thread. Abort () method of the thread has been called, but the thread has not stopped.
    • Background: The thread is executed in the background, which is related to the attribute thread. isbackground.
    • Running: The thread is running normally.
    • Stopped: the thread has been stopped.
    • Stoprequested: The thread is being requested to stop
    • Suincluded: the thread has been suspended. (In this status, you can call resume () to run the thread again)
    • Suspendrequested: The thread is requesting to be suspended, but cannot respond.
    • Unstarted: thread. Start () is not called to start the thread.
    • Waitsleepjoin: The thread is blocked because it calls methods such as wait (), sleep (), and join ().

As mentioned above, the background State indicates that the thread is running in the background. What are the special features of the backend running threads? In fact, there is only one difference between the background thread and the foreground thread, that is, the background thread does not prevent program termination. Once all foreground threads of a process are terminated, CLR (general language runtime environment) will completely terminate the process by calling the abort () method of any surviving background process.

When the threads compete for the CPU time, the CPU is given the service according to the priority of the thread. In the C # application, you can set five different priorities, from high to low, which are highest, abovenormal, normal, belownormal, and lowest. If the priority is not specified during thread creation, the default value is threadpriority. normal. Specify a priority for a thread
You can use the following code:

// Set the priority to the lowest
Mythread. Priority = threadpriority. Lowest;

By setting the thread priority, we can arrange some important threads for priority execution, such as user response.

Now we have a preliminary understanding of how to create and control a thread. Next we will study the typical problems in thread implementation in depth and discuss the solution.

III.Synchronization and communication of threads-producer and consumer

Assume that two threads maintain a queue at the same time. If one thread adds an element to the queue and the other thread uses the element from the queue, we call the thread for adding elements as the producer, and the thread for using elements as the consumer. The producer and consumer problems seem simple, but they are a problem that must be solved in multi-threaded applications. They involve synchronization and communication between threads.

As mentioned above, each thread has its own resources, but the code zone is shared, that is, each thread can execute the same function. However, in a multi-threaded environment, the possible problem is that several threads execute a function at the same time, resulting in data confusion and unexpected results. Therefore, we must avoid this situation. C # provides a keyword lock, which defines a piece of code as a critical section. A mutex section allows only one thread to enter the execution at a time point, other threads must wait. In C #, the keyword lock is defined as follows:

Lock (expression) statement_block

Expression indicates the object you want to trace. It is usually an object reference. Generally, if you want to protect an instance of a class, you can use this; if you want to protect a static variable (such as a mutex code segment inside a static method ), generally, you can use a class name. The statement_block is the code of the mutex, which can be executed by only one thread at a time.

The following is a typical example of using the lock keyword. I will explain the usage and usage of the lock keyword in the comment:

// Lock. CS
Using system;
Using system. Threading;

Internal class account
{
Int balance;
Random r = new random ();
Internal account (INT initial)
{
Balance = initial;
}

internal int withdraw (INT amount)
{< br> If (balance <0)
{< br> file: // if the balance is less than 0, an exception is thrown.
throw new exception ("negative balance ");
}< br> // The following Code ensures that the balance value is modified before the current thread modifies the balance value
// no other threads execute this code to modify the balance value.
// Therefore, the balance value cannot be less than 0
lock (this)
{< br> console. writeline ("current thread:" + thread. currentthread. name);
file: // If the lock keyword is not protected, the file may be executed after the if condition is determined.
file: // another thread executes the balance = balance-amount command to modify the balance value
file: // and this modification is invisible to this thread, therefore, the if condition is no longer true.
file: // However, this thread continues to execute balance = balance-amount, therefore, the balance may be less than 0
If (balance> = Amount)
{< br> thread. sleep (5);
balance = balance-amount;
return amount;
}< br> else
{< br> return 0; // Transaction rejected
}< BR >}< br> internal void dotransactions ()
{< br> for (INT I = 0; I <100; I ++)
withdraw (R. next (-50,100);
}< BR >}

Internal class test
{
Static internal thread [] threads = new thread [10];
Public static void main ()
{
Account ACC = new account (0 );
For (INT I = 0; I <10; I ++)
{
Thread t = new thread (New threadstart (Acc. dotransactions ));
Threads [I] = T;
}
For (INT I = 0; I <10; I ++)
Threads [I]. Name = I. tostring ();
For (INT I = 0; I <10; I ++)
Threads [I]. Start ();
Console. Readline ();
}
}

When multiple threads share an object, there will also be a problem similar to the Public Code. In this case, you should not use the lock keyword. here you need to use system. A kind of Monitor In threading is called a monitor, which provides a solution to share resources with threads.

The monitor class can lock an object. A thread can operate on this object only when this lock is obtained. The object lock mechanism ensures that only one thread can access this object at a time point that may cause confusion. Monitor must be associated with a specific object, but because it is a static class, it cannot be used to define the object, and all its methods are static, objects cannot be referenced. The following code uses monitor to lock an object:

......
Queue oqueue = new Queue ();
......
Monitor. Enter (oqueue );
... // Now the oqueue object can only be manipulated by the current thread
Monitor. Exit (oqueue); // release the lock

As shown above, when a thread calls monitor. when the enter () method locks an object, this object will be owned by it. Other threads want to access this object, only waiting for it to use monitor. exit () method to release the lock. To ensure that the thread can release the lock in the end, you can write the monitor. Exit () method into the finally code block in the try-catch-Finally structure. For any monitor-locked object, some information related to it is stored in the memory. One is the reference of the thread currently holding the lock, and the other is a reserve queue column, the queue stores the thread that is ready to obtain the lock. The third is a waiting queue, which stores the reference of the queue that is currently waiting for this object to change its status. When the thread that owns the object lock is about to release the lock, it uses monitor. the pulse () method notifies the first thread in the waiting queue, so the thread is transferred to the reserve queue column. When the object lock is released, the thread in the reserve queue column can immediately obtain the object lock.

The following is an example of how to use the lock keyword and the monitor class to synchronize and communicate threads. It is also a typical producer and consumer problem. In this routine, the producer thread and consumer thread are alternating. When the producer writes a number, the consumer immediately reads and displays it. I will introduce the essence of the program in the annotations. The system namespace used is as follows:

Using system;
Using system. Threading;

First, we define the cell class of the object to be operated. In this class, there are two methods: readfromcell () and writetocell. The consumer thread will call readfromcell () to read and display the cellcontents content. The producer process will call the writetocell () method to write data to cellcontents.

Public class Cell
{
Int cellcontents; // content in the cell object
Bool readerflag = false; // status flag. It can be read if it is true. If it is false, it is being written.
Public int readfromcell ()
{
Lock (this) // What is guaranteed by the lock keyword? Please refer to the previous introduction to lock.
{
If (! Readerflag) // if it cannot be read now
{
Try
{
File: // wait for the writetocell method to call the monitor. Pulse () method.
Monitor. Wait (this );
}
Catch (synchronizationlockexception E)
{
Console. writeline (E );
}
Catch (threadinterruptedexception E)
{
Console. writeline (E );
}
}
Console. writeline ("consume: {0}", cellcontents );
Readerflag = false; file: // resets the readerflag, indicating that the consumption has been completed.
Monitor. Pulse (this); file: // notify the writetocell () method (this method is executed in another thread, waiting)
}
Return cellcontents;
}

Public void writetocell (int n)
{
Lock (this)
{
If (readerflag)
{
Try
{
Monitor. Wait (this );
}
Catch (synchronizationlockexception E)
{
File: // when the synchronous method (the method of the monitor class except enter) is called in the non-Synchronous Code Area
Console. writeline (E );
}
Catch (threadinterruptedexception E)
{
File: // stop when the thread is waiting
Console. writeline (E );
}
}
Cellcontents = N;
Console. writeline ("produce: {0}", cellcontents );
Readerflag = true;
Monitor. Pulse (this); file: // notify another thread of the readfromcell () method being waited
}
}
}

The following defines the producer cellprod and consumer class cellcons. They all have only one method threadrun (), so that the threadstart proxy object provided to the thread in the main () function is used as the thread entry.

public class cellprod
{< br> cell; // The cell object to be operated
int quantity = 1; // number of producer productions, initialized to 1

Public cellprod (cell box, int request)
{< br> // constructor
cell = box;
quantity = request;
}< br> Public void threadrun ()
{< br> for (INT loty = 1; loty <= quantity; loty ++)
cell. writetocell (logoff); file: // The producer writes information to the operation object
}< BR >}

public class cellcons
{< br> cell;
int quantity = 1;

Public cellcons (cell box, int request)
{< br> cell = box;
quantity = request;
}< br> Public void threadrun ()
{< br> int valreturned;
for (INT loned = 1; looper <= quantity; loty ++)
valreturned = cell. readfromcell (); // The Consumer reads information from the operation object
}< BR >}

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.