POSIX thread (III)

Source: Internet
Author: User

Concurrent execution

Next we will write a program to check whether two threads are concurrently executed. Because we do not know the thread synchronization required to effectively complete this task, this is not a program that efficiently completes the POOL operation between threads. We will use this fact to share all variables except local function variables among different threads in a process.

Test-synchronous execution of two threads

In this part, the program thread2.c was simply modified by thread1.c. We add an additional file domain variable to test which thread is running:

Int run_now = 1;

When the main function is executed, we set it to 1, while when our new thread is executed, we set it to 2.

In the main function, after the new thread is created, we add the following code:

Int print_count1 = 0;
While (print_count1 ++ <20 ){
If (run_now = 1 ){
Printf ("1 ");
Run_now = 2;
}
Else {
Sleep (1 );
}
}

If run_now is 1, we output 1 and set it to 2. Otherwise, we will brief the sleep and perform a further test. We perform a one-time detection until the value is 1. This is called Busy waiting (busy wait), although we slow down by sleeping for one second between each detection. Later in this chapter, we will see a better way to complete this task.

In thread_function, this is the address of the new thread. We will perform the same operation, but the value is the opposite.

Int print_count2 = 0;
While (print_count2 ++ <20 ){
If (run_now = 2 ){
Printf ("2 ");
Run_now = 1;
}
Else {
Sleep (1 );
}
}

We also removed the passed parameters and returned values because we are not interested in them.

When we run this program, we will see the following output. (We may find that the program takes several seconds to generate the output)

$ CC-d_reentrant thread3.c-O thread2-lpthread
$./Thread2
12121212121212121212
Waiting for thread to finish...
Thread joined

Working Principle

Each thread notifies other threads of running by setting the run_now variable, and then waits for other threads to change the variable value. This demonstrates that thread execution is automatically transferred between two threads, and that the two threads share the run_now variable again.

Synchronization

In the previous section, we saw two threads execute at the same time, but the method for switching between them is clumsy and inefficient. Fortunately, there is a specially designed function set that provides us with better methods to control thread execution and access critical code.

We will understand two basic methods: semaphores, which act like a hacker around a piece of code; mutex, which acts like a mutex exclusive setting to protect code segments. These two methods are similar. Indeed, one of them can be implemented using another method. However, we recommend that you use one of the semantics in some cases. For example, to control access to shared memory that can only be accessed by one thread at a time, the most natural solution is to use mutex. However, controlling access to a set of identical objects as a whole. For example, specifying one of the five available telephone line sets to a thread is more suitable for counting semaphores. The method to choose depends on our preferences and the mechanism that best suits our program.

Use semaphores for synchronization

There are two interface functions used for semaphores: one from POSIX real-time extension and used for threads, and the other is known as the System V semaphores, which are usually used for process synchronization. (We will discuss the second semaphore later in this chapter) These two semaphores cannot interact with each other, and, despite being very similar, they use different function calls.

In this section, let's take a look at the simplest semaphore type. Its value is only a 0 or 1 binary semaphore. There is also a more general semaphores that use more-valued counting semaphores. Generally, semaphores are used to protect a piece of code so that only one execution thread can run it at any time. Binary semaphores are required for such tasks. Occasionally, we want to allow a certain number of threads to execute a specified piece of code. In this case, we can use a count semaphore. Because counting semaphores are not commonly used, we will not discuss them in depth here, but we need to point out that counting semaphores are only a logical extension of binary semaphores and the actual function calls required are the same.

The semaphore function does not start with pthread _, as is the case with most thread-specific functions, but with SEM. There are four basic semaphore functions used in the thread. They are all very simple.

A semaphore is created using the sem_init function. Its declaration is as follows:

# Include <semaphore. h>
Int sem_init (sem_t * SEM, int pshared, unsigned int value );

This function initializes a semaphore object directed by SEM, sets its sharing options, and specifies an initial integer for it. The pshared parameter controls the semaphore type. If the pshared value is 0, the semaphore is local for the current process. Otherwise, the semaphore can be shared among processes. Here we are only interested in semaphores that cannot be shared between processes. Linux does not support this type of sharing when writing this book, and the call fails if a non-zero value is passed for pshared.

The following function controls the semaphore value. Its declaration is as follows:

# Include <semaphore. h>
Int sem_wait (sem_t * SEM );
Int sem_post (sem_t * SEM );

Both functions use the pointer to the semaphore object initialized by the sem_init call as the parameter.

The sem_post function automatically increases the semaphore value by 1. The automatic here means that if two threads try to increase the value of a semaphore by 1 at the same time, they will not affect each other. For example, if the two programs read a value at the same time and increase the value, and this happens when writing this into a file. The semaphore always correctly increases its value by 2 because two threads are trying to modify it.

The sem_wait function automatically reduces the signal value by 1, but this function always waits until the semaphore has a non-zero count. Therefore, if we call the sem_wait function on a semaphore whose value is 2, the thread will continue to execute, but the semaphore value will be reduced to 1. If you call the sem_wait function on a semaphore whose value is 0, this function will wait until other functions increase the value so that the semaphore value is no longer 0. If two threads wait for the same semaphore to become non-zero in sem_wait at the same time, and the semaphore value is increased by the third process, then only one of the two waiting threads can reduce the semaphore and continue to execute, while the other will continue to wait.

The atomic "test and setting" ability in a function is the reason for making semaphores so valuable. There is another semaphore function, sem_trywait, which is the non-blocking mode of the sem_wait function. We will not discuss it in depth here. We can learn more about it in the manual.

The last semaphore function is sem_destroy. This function clears the semaphore when we finish. The statement is as follows:

# Include <semaphore. h>
Int sem_destroy (sem_t * SEM );

Once again, this function takes a pointer to the semaphore as a parameter and cleans up any of its resources. If we try to destroy a semaphore waiting for a thread, we will get an error.

Similar to most Linux functions, these functions return 0 upon success.

Test-thread semaphore

The following code, thread3.c, is also based on thread1.c. We have made a lot of changes, so here we will present them completely.

# Include <stdio. h>
# Include <unistd. h>
# Include <stdlib. h>
# Include <string. h>
# Include <pthread. h>
# Include <semaphore. h>

Void * thread_function (void * Arg );
Sem_t bin_sem;

# Define work_size 1024
Char work_area [work_size];

Int main ()
{
Int res;
Pthread_t a_thread;
Void * thread_result;

Res = sem_init (& bin_sem, 0, 0 );
If (res! = 0)
{
Perror ("semaphore initialization failed ");
Exit (exit_failure );
}
Res = pthread_create (& a_thread, null, thread_function, null );
If (res! = 0)
{
Perror ("thread creation failed ");
Exit (exit_failure );
}
Printf ("input some text. Enter 'end' to finish/N ");
While (strncmp ("end", work_area, 3 )! = 0)
{
Fgets (work_area, work_size, stdin );
Sem_post (& bin_sem );
}
Printf ("/nwaiting for thread to finish.../N ");
Res = pthread_join (a_thread, & thread_result );
If (res! = 0)
{
Perror ("thread join failed ");
Exit (exit_failure );
}
Printf ("thread joined/N ");
Sem_destroy (& bin_sem );
Exit (exit_success );
}

Void * thread_function (void * Arg)
{
Sem_wait (& bin_sem );
While (strncmp ("end", work_area, 3 )! = 0)
{
Printf ("You input % d characters/N", strlen (work_area)-1 );
Sem_wait (& bin_sem );
}
Pthread_exit (null );
}

The first important modification is to include semaphore. h so that we can access the semaphore function. However, we declare a semaphore and some variables and initialize the semaphore before creating a new thread.

Sem_t bin_sem;
# Define work_size 1024
Char work_area [work_size];
Int main (){
Int res;
Pthread_t a_thread;
Void * thread_result;
Res = sem_init (& bin_sem, 0, 0 );
If (res! = 0 ){
Perror ("semaphore initialization failed ");
Exit (exit_failure );
}

Note: here we initialize the semaphore value to 0.

In the main function, after we start a new thread, we read some text from the keyboard, store it in our work area, and then use the sem_post function to increase the semaphore.

Printf ("Input some text. Enter 'end' to finish/N ");
While (strncmp ("end", work_area, 3 )! = 0 ){
Fgets (work_area, work_size, stdin );
Sem_post (& bin_sem );
}

In the new thread, we wait for the semaphore and then calculate the number of characters entered.

Sem_wait (& bin_sem );
While (strncmp ("end", work_area, 3 )! = 0 ){
Printf ("you input % d characters/N", strlen (work_area)-1 );
Sem_wait (& bin_sem );
}

When the semaphore is set, we wait for the keyboard input. When we have some input, we release this semaphore, allowing the second thread to calculate the number of characters before the first thread reads again.

Again, the two threads share the same work_area array. We ignore some error detection, such as the return value of sem_wait, to make the code more concise. However, in production code, we should always check the returned error code unless we ignore these checks for good reasons.

Next we will run our program:

$ CC-d_reentrant-I/usr/include/nptl thread3.c-O thread3-L/usr/lib/nptl-
Lpthread
$./Thread3
Input some text. Enter 'end' to finish
The wasp Factory
You input 16 characters
Iain banks
You input 10 Characters
End
Waiting for thread to finish...
Thread joined

Working Principle

When we initialize the semaphore, we set its value to 0. Therefore, when the thread function is started, the sem_wait call will be blocked and wait for the semaphore to become non-zero.

In the main thread, we wait until we have some text, and then use the sem_post function to increase the semaphore, which immediately causes the other thread to return and start execution by sem_wait. Once the number of characters is calculated, the system calls sem_wait again and blocks it until the main thread calls sem_post again to increase the semaphore.

It is easy to ignore design errors that cause minor errors. Let's simply modify this program, thread4a. C, to indicate that the text entered by the keyboard is sometimes automatically replaced by available text. Modify the main function as follows:

Printf ("Input some text. Enter 'end' to finish/N ");
While (strncmp ("end", work_area, 3 )! = 0 ){
If (strncmp (work_area, "fast", 4) = 0 ){
Sem_post (& bin_sem );
Strcpy (work_area, "Wheeee ...");
} Else {
Fgets (work_area, work_size, stdin );
}
Sem_post (& bin_sem );
}

Now, if we enter fast, the program will call sem_post to allow the character counter to run, but it will use some different content to update work_area immediately.

$ CC-d_reentrant thread4a. C-o thread4a-lpthread
$./Thread4a
Input some text. Enter 'end' to finish
Excession
You input 9 Characters
Fast
You input 7 characters
You input 7 characters
You input 7 characters
End
Waiting for thread to finish...
Thread joined

The problem is that our program relies on text input in another thread to complete word statistics before the main thread is ready to send more words to it for calculation. When we try to quickly and consecutively specify two different word sets for them to count (enter fast on the keyboard and then replace it with Wheee automatically ...), there is no time for execution in the second thread. However, the semaphore has been added multiple times, so the counter thread will continue to count the word and reduce the semaphore value until it becomes zero again.

This example shows that we need to carefully consider the time in a multi-threaded program. You can use another semaphore to wait for the main thread until the counting thread has the opportunity to complete its count to fix this problem.

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.