In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-02-24 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/01 Report--
This article mainly explains the "Linux multithreaded programming case analysis", the content of the article is simple and clear, easy to learn and understand, the following please follow the editor's ideas slowly in depth, together to study and learn "Linux multithreaded programming case analysis" bar!
Thread
Thread is the smallest unit that runs independently in a computer, and the runtime takes up very little system resources. You can think of threads as the basic unit in which the operating system allocates cpu time. A process can have at most one thread. Its threads share resources such as address space and open file descriptors within the process. At the same time, the thread also has its own private data information, including: thread number, register (program counter and stack pointer), stack, signal mask, priority, thread private storage space.
Why introduce threads after having the concept of process? What are the benefits of using multithreading? What kind of system should choose multithreading?
One of the reasons for using multithreading is that it is a very "frugal" multitasking operation compared to processes. We know that under the linux system, starting a new process must be assigned to its independent address space, and establish a large number of data tables to maintain its code segment, stack segment and data segment, which is an "expensive" multitasking way. While multiple threads running in a process use the same address space with each other and share most of the data, the space it takes to start a thread is much less than the space it takes to start a process, and the time it takes for threads to switch from one to another is also much less than the time it takes to switch between processes. According to statistics, overall, the cost of a process is about 30 times that of a thread, and of course, this data may vary greatly on a specific system.
The second reason for using multithreading is the convenient communication mechanism between threads. For different processes, they have independent data space, and data transmission can only be carried out through communication, which is not only time-consuming, but also very inconvenient. Threads are not, because the data space is shared between threads in the same process, so the data of one thread can be directly used by other threads, which is not only fast, but also convenient. Of course, the sharing of data also brings some other problems, some variables cannot be modified by two threads at the same time, and the data declared as static in some subroutines are more likely to bring a catastrophic blow to multithreaded programs, which are the most important things to pay attention to when writing multithreaded programs.
In addition to the advantages mentioned above, without comparing with processes, multithreaded programs, as a multitasking and concurrent way of working, certainly have the following advantages:
1) improve the response of the application. This is particularly meaningful for graphical interface programs, when an operation takes a long time, the whole system will wait for this operation, when the program will not respond to keyboard, mouse, menu operations, but the use of multi-threading technology, time-consuming operations (time consuming) placed in a new thread, can avoid this awkward situation.
2) make the multi-cpu system more effective. The operating system ensures that when the number of threads is not greater than the number of cpu, different threads run on different cpu.
3) improve the program structure. A long and complex process can be divided into several threads and become several independent or semi-independent running parts. such a program will be easy to understand and modify.
Thread classification
According to its scheduler, threads can be divided into user-level threads and core-level threads.
(1) user-level thread
User-level thread mainly solves the problem of context switching, and its scheduling algorithm and scheduling process are all decided by the user, and do not need specific kernel support at run time. Here, the operating system often provides a user-space thread library, which provides thread creation, scheduling, revocation and other functions, while the kernel still only manages the process. If a thread in a process calls a blocked system call, the process, including all other threads in the process, is also blocked. The main disadvantage of this user-level thread is that it can not take advantage of multiprocessors in the scheduling of multiple threads in a process.
(2) Core level thread
This kind of thread allows threads in different processes to be scheduled according to the same relative priority scheduling method, so that they can take advantage of the concurrency of multiprocessors.
Nowadays, most systems adopt the method of coexistence of user-level thread and core-level thread. A user-level thread can correspond to one or more core-level threads, that is, an one-to-one or many-to-one model. This can not only meet the needs of multiprocessor systems, but also minimize the scheduling overhead.
Linux implementation created by thread
As we know, the threading implementation of linux is done outside the core, and what is provided in the core is the interface do_fork () to create the process. The kernel provides two system calls, clone () and fork (), which end up calling api in the do_fork () kernel with different parameters. Of course, it is impossible to implement threads without core support for shared data segments by multiple processes (actually lightweight processes), so do_fork () provides a lot of parameters, including clone_vm (shared memory space), clone_fs (shared file system information), clone_files (shared file descriptor table), clone_sighand (shared signal handle table) and clone_pid (shared process id, only for in-kernel processes. That is, process 0 is valid. When using the fork system call, the kernel call do_fork () does not use any shared properties, and the process has an independent running environment, and when you use pthread_create () to create a thread, all these properties are finally set to call _ _ clone (), and all these parameters are passed to do_fork () in the kernel, so that the created "process" has a shared running environment, and only the stack is independent, passed in by _ _ clone ().
Linux threads exist in the form of lightweight processes in the core and have independent process table items, while all creation, synchronization, deletion and other operations are carried out in the out-of-core pthread library. The pthread library uses a management thread (_ _ pthread_manager (), each process is independent and unique) to manage thread creation and termination, assigns thread id to the thread, and sends thread-related signals (such as cancel), while the caller of the main thread (pthread_create ()) pipelines the request information to the management thread.
Multithreaded programming
1. Creation and exit of threads
Pthread_create thread creation function
Int pthread_create (pthread_t * thread_id,__const pthread_attr_t * _ _ attr,void * (* _ _ start_routine) (void *), void * _ restrict _ _ arg)
The first parameter of the thread creation function is the pointer to the thread identifier, the second parameter is used to set the thread properties, the third parameter is the starting address of the thread running function, and the last parameter is the parameter of the running function. Here, our function thread takes no arguments, so the last parameter is set to a null pointer. We also set the second parameter to a null pointer, which will generate the thread of the default property. When the thread is created successfully, the function returns 0. If it is not 0, the thread creation failed. The common error return codes are eagain and einval. The former indicates that the system restricts the creation of new threads, such as too many threads; the latter indicates that the value of the thread property represented by the second parameter is illegal. After the thread is created successfully, the newly created thread runs the function determined by parameters 3 and 4, and the original thread continues to run the next line of code.
The pthread_join function to wait for the end of a thread.
Function prototype is: int pthread_join (pthread_t _ _ th, void * * _ thread_return)
The first parameter is the identifier of the waiting thread, and the second parameter is a user-defined pointer that can be used to store the return value of the waiting thread. This function is a thread blocking function, and the function that calls it will wait until the end of the waiting thread, and when the function returns, the resources of the waiting thread will be reclaimed. The thread can only be terminated by one thread and should be in the joinable state (not detached).
Pthread_exit function
There are two ways to end a thread. One is that when the function that the thread runs ends, so does the thread that calls it.
Another way is through the function pthread_exit. Its function prototype is: void pthread_exit (void * _ retval) the only argument is the function return code, as long as the second parameter thread_return in pthread_join is not null, this value will be passed to thread_return. Finally, a thread cannot be waited by multiple threads, otherwise the first thread that receives the signal returns successfully, and the rest of the threads that call pthread_join return the error code esrch.
2. Thread attributes
The property of the thread, the second parameter of the pthread_create function. Set this value to null, that is, using the default property, and multiple properties of the thread can be changed. These attributes mainly include binding attributes, detaching attributes, stack address, stack size, and priority. The default properties of the system are unbound, non-detached, default 1m stack, and priority at the same level as the parent process. First of all, we will explain the basic concepts of binding and separation properties.
Binding properties: linux uses a "one-to-one" threading mechanism, that is, one user thread corresponds to one kernel thread. Binding attribute means that a user thread is permanently assigned to a kernel thread, because the scheduling of cpu time slices is oriented to kernel threads (that is, lightweight processes), so threads with binding attributes can ensure that there is always a kernel thread corresponding to it when needed. In contrast, the unbound attribute means that the relationship between the user thread and the kernel thread is not always fixed, but is controlled by the system.
Detach attributes: detach attributes are used to determine how a thread terminates itself. In the case of non-separation, when a thread ends, the system resources it occupies are not released, that is, there is no real termination. Only when the pthread_join () function returns can the created thread release the system resources it occupies. In the case of separating attributes, a thread releases the system resources it occupies immediately when it ends.
One thing to note here is that if you set the detach property of a thread, and the thread runs very fast, it is likely to terminate before the pthread_create function returns, and after it terminates, it may hand over the thread number and system resources to other threads, and the thread calling pthread_create gets the wrong thread number.
Set the binding properties:
Int pthread_attr_init (pthread_attr_t * attr) int pthread_attr_setscope (pthread_attr_t * attr, int scope) int pthread_attr_getscope (pthread_attr_t * tattr, int * scope)
Scope:pthread_scope_system: binding, this thread competes with all threads in the system
Pthread_scope_process: unbound, this thread competes with other threads in the process
Set the detach properties:
Int pthread_attr_setdetachstate (pthread_attr_t * attr,int detachstate) int pthread_attr_getdetachstate (const pthread_attr_t * tattr,int * detachstate)
Detachstate pthread_create_detached: detach
Pthread _ create_joinable: non-detached
Set the scheduling policy:
Int pthread_attr_setschedpolicy (pthread_attr_t * tattr, int policy) int pthread_attr_getschedpolicy (pthread_attr_t * tattr, int * policy)
Policy sched_fifo: first in, first out
Sched_rr: cyclin
Sched_other: a method to implement a definition
Set the priority:
Int pthread_attr_setschedparam (pthread_attr_t * attr, struct sched_param * param) int pthread_attr_getschedparam (pthread_attr_t * attr, struct sched_param * param)
3. Thread access control
1) Mutex (mutex)
The synchronization between threads is realized through the lock mechanism. Only one thread is allowed to execute a critical part of the code at a time.
Int pthread_mutex_init (pthread_mutex_t * mutex,const pthread_mutex_attr_t * mutexattr); int pthread_mutex_lock (pthread_mutex_t * mutex); int pthread_mutex_unlock (pthread_mutex_t * mutex); int pthread_mutex_destroy (pthread_mutex_t * mutex)
(1) initialize lock init () or static assignment pthread_mutex_t mutex=pthread_mutex_initialier first
(2) Lock is added. Lock,trylock,lock blocks and waits for lock. Trylock returns ebusy immediately.
(3) when unlocked, the unlock needs to be in the locked state and unlocked by the locking thread
(4) clear the lock, destroy (lock must be unlock at this time, otherwise ebusy is returned)
Mutex is divided into recursive (recursive) and non-recursive (non-recursive), this is the name of posix, another name is reentrant and non-reentrant. There is no difference between the two mutex as synchronization tools for inter-thread. The only difference is that the same thread can lock the recursive mutex repeatedly, but not the non-recursive mutex.
The preferred non-recursive mutex is definitely not for performance, but for design intent. There is not much difference in performance between non-recursive and recursive, because the former is only slightly faster with one less counter. Locking non-recursive mutex multiple times in the same thread immediately leads to deadlocks, which I think is an advantage that can help us think about code's expectations for locks and find problems early (in the coding phase). There is no doubt that recursive mutex is easier to use because you don't have to worry about a thread locking itself, which I guess is why java and windows provide recursive mutex by default. (the intrinsic lock that comes with the java language is reentrant, and so is the critical_section that provides reentrantlock,windows in its concurrent library. None of them seem to provide lightweight non-recursive mutex. )
2) conditional variable (cond)
A mechanism for synchronization using global variables shared between threads.
Int pthread_cond_init (pthread_cond_t * cond,pthread_condattr_t * cond_attr); int pthread_cond_wait (pthread_cond_t * cond,pthread_mutex_t * mutex); int pthread_cond_timedwait (pthread_cond_t * cond,pthread_mutex_t * mutex,const timespec * abstime); int pthread_cond_destroy (pthread_cond_t * cond); int pthread_cond_signal (pthread_cond_t * cond); int pthread_cond_broadcast (pthread_cond_t * cond) / / unblock all threads
(1) initialization. Init () or pthread_cond_t cond=pthread_cond_initialier; property is set to null
(2) waiting for the conditions to be established. Pthread_cond_wait,pthread_cond_timedwait.
Wait () releases the lock and blocks waiting for the condition variable to be true
Timedwait () sets the wait time, but still does not signal, and returns etimeout (lock guarantees that there is only one thread wait)
(3) Activation condition variable: pthread_cond_signal,pthread_cond_broadcast (activate all waiting threads)
(4) clear the condition variable: destroy; wireless wait, otherwise return ebusy
Int pthread_cond_wait (pthread_cond_t * cond, pthread_mutex_t * mutex)
Int pthread_cond_timedwait (pthread_cond_t * cond, pthread_mutex_t * mutex, const struct timespec * abstime)
Be sure to use it in the locked area of the mutex.
When you call pthread_cond_signal () to release a thread that is blocked by a condition, calling pthread_cond_signal () does not work if no thread is blocking based on the condition variable. For windows, when calling setevent triggers the event condition of auto-reset, if there is no thread blocked by the condition, then the function still works and the condition variable is in the triggered state.
Use conditional variables to realize the "producer-consumer problem":
# include#include#include#include "pthread.h" # define buffer_size 16 struct prodcons {int buffer [buffer _ size]; pthread_mutex_t lock; / / mutex ensuring exclusive access to buffer int readpos,writepos; / / position for reading and writing pthread_cond_t notempty; / / signal when buffer is notempty pthread_cond_t notfull; / / signal when buffer is notfull} / / initialize a buffervoid init (struct prodcons* b) {pthread_mutex_init (& b-> lock,null); pthread_cond_init (& b-> notempty,null); pthread_cond_init (& b-> notfull,null); b-> readpos=0; b-> writepos=0;} / / store an integer in the buffervoid put (struct prodcons* b, int data) {pthread_mutex_lock (& b-> lock) / / wait until buffer is notfull while ((b-> writepos+1)% buffer_size==b- > readpos) {printf ("wait for notfull\ n"); pthread_cond_wait (& b-> notfull,&b- > lock);} b-> buffer [b-> writepos] = data; b-> writepos++; pthread_cond_signal (& b-> notempty); / / signal buffer is notempty pthread_mutex_unlock (& b-> lock) } / / read and remove an integer from the bufferint get (struct prodcons* b) {int data; pthread_mutex_lock (& b-> lock); / / wait until buffer is notempty while (b-> writepos==b- > readpos) {printf ("wait for notempty\ n"); pthread_cond_wait (& b-> notempty,&b- > lock);} data=b- > buffer [b-> readpos]; b-> readpos++; if (b-> readpos > = buffer_size) b-> readpos=0 Pthread_cond_signal (& b-> notfull); / / signal buffer is notfull pthread_mutex_unlock (& b-> lock); return data;} # define over-1 struct prodcons buffer; void * producer (void * data) {int n; for; sem_wait (& b-> pmut); b-> buf [b-> nextin] = item; b-> nextin++; b-> nextin% = bsize; sem_post (& b-> pmut); sem_post (& b-> occupied) } char consumer (buffer_t * b) {char item; sem_wait (& b-> occupied); sem_wait (& b-> cmut); item = b-> buf [b-> nextout]; b-> nextout++; b-> nextout% = bsize; sem_post (& b-> cmut); sem_post (& b-> empty); return (item) Thank you for your reading, the above is the content of "Linux multithreaded programming example analysis". After the study of this article, I believe you have a deeper understanding of the problem of Linux multithreaded programming example analysis, and the specific use needs to be verified in practice. Here is, the editor will push for you more related knowledge points of the article, welcome to follow!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.