-posix Threading in Linux

Source: Internet
Author: User

A simple tool to support memory sharing excerpt from https://www.ibm.com/developerworks/cn/linux/thread/posix_thread1/

Threads are interesting

Knowing how to use threads correctly is an essential quality for every good programmer. Threads are similar to processes. Like a process, threads are managed by the kernel by time sharding. In a single-processor system, the kernel uses time shards to simulate the concurrent execution of threads in the same way as processes. In multiprocessor systems, as with multiple processes, threads can actually execute concurrently.

So why is multithreading more advantageous than multiple independent processes for most collaborative tasks? This is because threads share the same memory space. Different threads can access the same variable in memory. Therefore, all threads in the program can read or write the declared global variables. If you've ever written important Code with fork (), you'll recognize the importance of this tool. Why is it? While fork () allows you to create multiple processes, it also brings up the following communication issues: how to make multiple processes communicate with each other, where each process has its own separate memory space. There is no simple answer to this question. Although there are many different kinds of local IPC (interprocess communication), they all encounter two important obstacles:

    • Imposes some form of additional kernel overhead, which reduces performance.
    • For most cases, IPC is not a "natural" extension of the code. The complexity of the program is often greatly increased.

Double bad: Both overhead and complexity are not good. If you've ever tried to support the IPC, you'll really appreciate the simple shared memory mechanism provided by the thread. Because all threads reside in the same memory space, POSIX threads do not need to make long-distance calls with large overhead and complexity. As long as the simple synchronization mechanism is used, all the threads in the program can read and modify the existing data structures. Without having to dump or squeeze data through a file descriptor into a tight, shared memory space. For this reason alone, you should consider a single-process/multithreaded mode instead of multi-process/single-threaded mode.

Threads are fast.

Not only this. Threads are also very fast. The overhead of threading is small compared to standard fork (). The kernel does not need to copy the process's memory space or file descriptors separately, and so on. This saves a lot of CPU time, making thread creation 10 to 100 times times faster than new process creation. Because of this, you can use a lot of threads without worrying too much about the CPU or out of memory. The large amount of CPU usage that results from using fork () no longer exists. This means that a thread can usually be created as long as it makes sense in the program.

Of course, as with processes, threads will take advantage of multiple CPUs. If the software is designed for multiprocessor systems, this is really a feature (if the software is open source, it may end up running on quite a few platforms). The performance of a particular type of thread program, especially CPU-intensive, will increase almost linearly with the number of processors in the system. If you are writing a very CPU-intensive program, you absolutely want to try to use multithreading in your code. Once you have mastered thread coding, you can solve coding challenges in new and creative ways without the use of cumbersome IPC and other complex communication mechanisms. All of these features work together to make multithreaded programming more fun, fast, and flexible.

Threads are portable

If you are familiar with Linux programming, it is possible to know the __clone () system call. __clone () is similar to fork () and also has many characteristics of threads. For example, with __clone (), a new child process can selectively share the execution environment (memory space, file descriptor, and so on) of the parent process. This is the good side. But __clone () also has shortcomings. As __clone () online Help points out:

The __clone call is specific to the Linux platform and does not apply to implementing a portable program. To write threaded applications (multithreading controls the same memory space), it is best to use libraries that implement the POSIX 1003.1c threading API, such as the Linux-threads library. See Pthread_create (3THR). "

Although there are many features of the __clone () thread, it is not portable. Of course this does not mean that it cannot be used in code. However, this fact should be weighed when considering the use of __clone () in the software. Thankfully, as __clone () online help points out, there is a better alternative: POSIX threading. If you want to write portable multithreaded code that can run on Solaris, FreeBSD, Linux, and other platforms, POSIX threading is a choice of course.

First thread

Here is a simple example program for a POSIX thread:

thread1.c
#include <pthread.h> #include <stdlib.h> #include <unistd.h> void *thread_function (void *arg) {  int i;  for (i=0; i<20; i++) {    printf ("Thread says hi!\n");    Sleep (1);  }  return NULL;} int main (void) {  pthread_t mythread;    if (pthread_create (&mythread, NULL, thread_function, NULL)) {    printf ("Error creating thread.");    Abort ();  }  if (Pthread_join (Mythread, NULL)) {    printf ("Error joining thread.");    Abort ();  }  Exit (0);}

To compile this program, simply save the program as THREAD1.C and enter:

$ gcc Thread1.c-o thread1-lpthread

Run then enter:

$./thread1
Understanding THREAD1.C

Thread1.c is a very simple threaded thread. Although it does not implement any useful functionality, it can help understand the threading mechanism. Below, we step-by-step to understand what this program is doing. The variable mythread is declared in Main (), and the type is pthread_t. The pthread_t type is defined in Pthread.h, often referred to as the "Thread ID" (abbreviated as "Tid"). You can think of it as a thread handle.

After Mythread declaration (remember that Mythread is just a "tid" or a handle to the thread that will be created), call the Pthread_create function to create a real active thread. Do not be fooled by pthread_create () within an "if" statement. Because Pthread_create () returns zero when execution succeeds and fails, it returns a non-0 value, placing the Pthread_create () function call in the IF () statement just to easily detect a failed call. Let's take a look at the pthread_create parameter. The first parameter, &mythread, is a pointer to Mythread. The second parameter is currently NULL and can be used to define some properties of the thread. Because the default thread property is applicable, simply set the parameter to NULL.

The third parameter is the function name that is called when a new thread starts. In this example, the function is named Thread_function (). When Thread_function () returns, the new thread terminates. In this case, the thread function does not implement a large function. It only outputs "Thread says hi!" 20 times and then exits. Note that thread_function () accepts void * As a parameter, and the type of the return value is also void *. This indicates that any type of data can be passed to the new thread with void *, and any type of data can be returned when the new thread finishes. So how do you pass an arbitrary argument to a thread? Very simple. Just take advantage of the fourth parameter in Pthread_create (). In this example, the fourth parameter is set to NULL because there is no need to pass any data to a trivial thread_function ().

You may have speculated that after Pthread_create () returns successfully, the program will contain two threads. Wait a minute, two threads? Didn't we just create a thread? Yes, we only created a process. But the main program is also a thread. It can be understood that if you write a program that does not use a POSIX thread at all, the program is single-threaded (this single thread is called the "primary"). After creating a new thread, the program has a total of two threads.

I think at this point you have at least two important questions. The first issue is how the main thread runs after the new threads are created. Answer, the main thread continues to execute the next line of programs sequentially (in this case, "if (Pthread_join (...)") )。 The second question is what to do when a new thread ends. The answer, the new thread stops first, and then waits for a merge or "connect" with another thread as part of its cleanup process.

Now, take a look at Pthread_join (). Just as Pthread_create () splits a thread into two, Pthread_join () merges two threads into a single thread. The first parameter of Pthread_join () is the Tid mythread. The second parameter is a pointer to a void pointer. If the void pointer is not null,pthread_join, the void * return value of the thread is placed at the specified location. Since we do not have to ignore the return value of Thread_function (), it is set to NULL.

You will notice that Thread_function () took 20 seconds to complete. Long before the end of Thread_function (), the main thread has called Pthread_join (). If this happens, the main thread will break (turn to sleep) and wait for thread_function () to complete. When Thread_function () finishes, Pthread_join () will return. Then there is only one main thread of the program. When the program exits, all new threads have been merged using Pthread_join (). This is how you should handle each new thread that you create in your program. If you do not merge a new thread, it still has a negative limit on the maximum number of threads in the system. This means that if the thread is not properly cleaned, the pthread_create () call will eventually fail.

No father, No child

If you have used a fork () system call, you may be familiar with the concepts of parent and child processes. When you create another new process with fork (), the new process is a child process, and the original process is the parent process. This creates a potentially useful hierarchical relationship, especially when the child process is terminated. For example, the Waitpid () function lets the current process wait for all child processes to terminate. Waitpid () is used to implement a simple cleanup process in the parent process.

POSIX threads are more interesting. You may have noticed that I have been deliberately avoiding the use of "Parent threads" and "Child threads." This is because this hierarchical relationship does not exist in POSIX threads. Although the main thread can create a new thread, the new thread can create another new thread, and the POSIX threading standard treats them as an equal level. So the notion of waiting for a child thread to exit is meaningless here. The POSIX threading standard does not log any "family" information. The lack of family information has a major implication: if you wait for a thread to terminate, you must pass the TID of the thread to Pthread_join (). Line libraries cannot determine the TID for you.

This is not good news for most developers, because it complicates programs that have multiple threads. But don't worry about it. The POSIX threading Standard provides all the tools needed to effectively manage multiple threads. In fact, the fact that there is no parent/child relationship opens up a more creative way to use threads in a program. For example, if you have a thread called thread 1 and thread 1 creates a thread called Thread 2, thread 1 does not have to call Pthread_join () to merge thread 2, which can be done by any of the other threads in the program. This can allow interesting things to happen when you write code that uses a lot of threads. For example, you can create a global "dead-thread list" that contains all of the stopped threads, and then have a dedicated cleanup thread, such as a stopped thread, added to the list. This cleanup thread calls Pthread_join () to merge the newly-stopped thread with itself. Now, all cleanup is handled smartly and efficiently with just one thread.

Synchronous roaming

Now let's take a look at some of the code that did something unexpected. The code for THREAD2.C is as follows:

Thread2.c
#include <pthread.h> #include <stdlib.h> #include <unistd.h> #include <stdio.h>int myglobal; void *thread_function (void *arg) {  int i,j;  for (i=0; i<20; i++) {    j=myglobal;    j=j+1;    printf (".");    Fflush (stdout);    Sleep (1);    myglobal=j;  }  return NULL;} int main (void) {  pthread_t mythread;  int i;  if (pthread_create (&mythread, NULL, thread_function, NULL)) {    printf ("Error creating thread.");    Abort ();  }  for (i=0; i<20; i++) {    myglobal=myglobal+1;    printf ("O");    Fflush (stdout);    Sleep (1);  }  if (Pthread_join (Mythread, NULL)) {    printf ("Error joining thread.");    Abort ();  }  printf ("\nmyglobal equals%d\n", myglobal);  Exit (0);}
Understanding THREAD2.C

Like the first program, this program creates a new thread. Both the main thread and the new thread add the global variable Myglobal 120 times. But the program itself has some unintended consequences. To compile the code, enter:

$ gcc Thread2.c-o thread2-lpthread

Run Please enter:

$./thread2

Output:

$./thread2. O.O.O.O.OO.O.O.O.O.O.O.O.O.O. O.o.o.o.omyglobal equals 21

It was a surprise! Since Myglobal is zero-based, the main thread and the new thread each add one to it 20 times, and the Myglobal value at the end of the program should be equal to 40. Because the Myglobal output is 21, there is definitely a problem. But what exactly is it?

Give It up? Well, let me explain what's going on. First look at the function thread_function (). Notice how to copy the Myglobal to the local variable "J"? Then add J to one, sleep for a second, and then copy the new J value to Myglobal? That's the point. Imagine what would happen if the main thread were to add Myglobal immediately after the new threads copied the Myglobal value to J. When Thread_function () writes the value of J back to Myglobal, the changes made by the main thread are overwritten.

When writing a thread program, avoid this useless side effect, otherwise it will only waste time (of course, it is useful in addition to writing articles about POSIX threads). So, how can we get rid of this problem?

Since the problem occurs when you copy Myglobal to J and wait a second before writing back, you can try to avoid using temporary local variables and add Myglobal directly. While this solution applies to this particular example, it is still not correct. If we perform relatively complex mathematical operations on Myglobal, rather than simply adding one, this method will fail. But why?

To understand this problem, you must remember that threads are running concurrently. Even running on a single-processor system (the kernel leverages time-slicing to simulate multitasking) is also possible, from the programmer's point of view, to imagine that two threads are executed concurrently. The problem with thread2.c is that thread_function () relies on the argument that Myglobal will not be modified for about a second before Myglobal plus one. There is a need for some way for a thread to notify other threads of "don't approach" when making changes to Myglobal. I'll explain how to do that in the next article. See you then.

-posix Threading in Linux

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.