The article directories
-
- preface
- Abstract
- thread
-
- What is a thread
- The advantages of using threads
- Threads and processes are intertwined
- Resource sharing between threads
- Disadvantages of using threads
- Thread Management
-
- Create a thread
- Gets the current thread ID
- Determine whether two threads are equal
- Connecting and Detaching threads
- Thread attributes
- The mutex
-
- Why mutex exists
- Mutex primitives
- Parameter definition
- Mutex usage
- A deadlock
- Lock of
-
- Optimistic locking
- Pessimistic locking
- Optimistic locks vs. pessimistic locks
- Spin locks &&mutex
- Condition variables,
-
- Conditional variable primitives
- Condition variables and mutex
-
- Matters needing attention
- False wake up and lost wake up
-
- ⑴ False awakening
- ⑵ Wake up lost
- Use condition variables
- The thread pool
- Set pieces
-
- Pthread API functions
- Object creation in multithreading
- Object destruction and race conditions
-
- shared_ptr/weak_ptr
- More on C++ memory security
- Resources to recommend
preface
Unconsciously, to the junior. Before you know it, you’re looking for a summer internship. Look at the old and you’ll know the new. (After reviewing the data structure for two days, I still prefer this one.) So here we are.
Abstract
In architectures with shared memory for multiple processors (e.g., symmetric multiprocessing system SMP), threads can be used to achieve program parallelism. Hardware vendors have historically implemented proprietary versions of multithreaded libraries, forcing software developers to be concerned about portability. For UNIX systems, the IEEE POSIX 1003.1 standard defines a C multithreaded programming interface. Implementations that are attached to the standard are called POSIX TheADS or Pthreads.
This tutorial introduces the concept, motivation, and design of Pthreads. It covers the three main categories of Pthreads API functions: Thread Managment, Mutex Variables, and Condition Variables. Examples are provided to programmers who are just beginning to learn Pthreads.
Suitable for: beginning to learn parallel programming using threads; Basic knowledge of C parallel programming.
thread
They say know what you are, know why you are. I don’t know. That’s what we’re supposed to do.
What is a thread
Officially, it is the smallest unit in which an operating system can schedule operations. It is contained within the process and is the actual operating unit within the process. A thread is a single sequential flow of control in a process, and multiple threads can be concurrent in a process, each performing a different task in parallel.
1, improve the concurrency of the program2, low overhead, no need to reallocate memory3, easy to communicate and share dataCopy the code
The advantages of using threads
- All threads in the same process share the same address space. In many cases, interthread communication is more efficient and easier to use than interprocess communication. Applications that use threads have potential performance gains and practical advantages over applications that do not use threads:
- The CPU works with I/O overlap: For example, a program may have an I/O operation that takes a long time, and while one thread is waiting for the I/O system call to complete, the CPU can be used by other threads.
- Priority/real-time scheduling: More important tasks can be scheduled, replaced, or interrupted by lower-priority tasks.
- Asynchronous event processing: Tasks with uncertain frequency and duration can be interleaved. For example, a Web server can simultaneously transfer data for a previous request and manage a new one.
- Pthreads has no intermediate memory replication because threads and a process share the same address space. There is no data transfer. To cache-to-CPU or memory-to-CPU bandwidth (worst case), the speed is quite fast.
The disadvantages, the disadvantages are obvious, the disadvantages are mixed, but more on that later.
Threads and processes are intertwined
(1) Thread is also called lightweight process, also has PCB, thread creation using the same low-level function and process, are clone. (2Threads and processes look the same from the kernel, each with a different PCB, but the PCB points to a different three-level page table of memory resources. (3A process can be morphed into a thread, or a main thread, which is the main road of a highway. (4Under Linux, a thread is the smallest unit of execution and a process is the smallest unit of allocated resources.Copy the code
Resource sharing between threads
⑴ Sharing resources
File descriptors table 2. Processing method for each signal 3. Current working directory 4Copy the code
(2) Non-shared resources
Thread ID 2, processor field and stack pointer 3, independent stack space 4, errno variable 5, signal masking word 6, scheduling priorityCopy the code
Disadvantages of using threads
1Thread instability (this is really unstable, there will be an article on the effects of reentrant functions on threads, because I haven't sorted that out yet)2Thread debugging is difficult (this is a real headache, difficult to debug things, so far I only have a "segment error, core has been dumped" can be used, the key is that the error is difficult to reproduce, difficult, difficult)3Threads cannot use classic Unix events such as signals.Copy the code
For example, suppose your program creates several threads, each calling the same library function:
This library function accesses/modifies a global structure or location in memory. When each thread calls this function, the global structure live memory location may be modified at the same time. If the function does not use synchronization to prevent data corruption, then it is not thread-safe.
If you are not 100% sure that external library functions are thread-safe, you are responsible for any problems that may arise. Suggestion: Be careful when using libraries or objects that are not explicitly thread-safe. If in doubt, assume it is not thread-safe until proven. You can find the problem by constantly using the indeterminate function.Copy the code
Take a look at this article (to be rewritten in a few days) : What reentrant functions mean for thread-safety
Thread Management
Create a thread
#include<pthread.h>
int pthread_create(pthread_t *thread,const pthread_tattr_t *attr,void *(*start_routine)(void *),void *arg);
/* Thread: this is an outgoing argument that passes a pthread_t variable, which is used to store the tid of the new thread. Attr: this is a thread property setting. NULL indicates that the default value is used. Function pointer to a new thread should point to arg function modules: old ripe, to the preceding function and use of, don't preach and then write a NULL return value: returns 0. Success, failure return error, error number and error number, said earlier errno is not Shared. Note (1) : There are no special cases in which we use the default properties when creating a thread, but sometimes we need to do something special, such as adjust the priority. * /
Copy the code
Q: How do YOU safely pass data to a newly created thread? A: Ensure that the data being passed is thread-safe (cannot be modified by other threads). Here are three examples of what should and shouldn’t be.
Code demo:
// Example Code - Pthread Creation and Termination
#include <pthread.h>
#include <stdio.h>
#define NUM_THREADS 5
void *PrintHello(void *thread_id)
{
int tid;
tid = (int)thread_id;
printf("Hello World! It's me, thread #%d! \n", tid);
pthread_exit(NULL);
}
int main(int argc, char *argv[])
{
pthread_t threads[NUM_THREADS];
int rc, t;
for(t=0; t<NUM_THREADS; t++){
printf("In main: creating thread %d\n", t);
rc = pthread_create(&threads[t], NULL, PrintHello, (void *)t);
if (rc){
printf("ERROR; return code from pthread_create() is %d\n", rc);
exit(- 1); }}pthread_exit(NULL);
}
Copy the code
Next to demonstrate thread safety:
// The following snippet demonstrates how to pass a simple integer to a thread.
// The main thread uses a unique data structure for each thread to ensure that the parameters passed by each thread are complete.
int *taskids[NUM_THREADS];
for(t=0; t<NUM_THREADS; t++)
{
taskids[t] = (int *) malloc(sizeof(int));
*taskids[t] = t;
printf("Creating thread %d\n", t);
rc = pthread_create(&threads[t], NULL, PrintHello,(void*) taskids[t]); . }Copy the code
// This example shows how to set/pass parameters to a thread using a structure. Each thread gets a unique instance of a structure.
struct thread_data{
int thread_id;
int sum;
char *message;
};
struct thread_data thread_data_array[NUM_THREADS];
void *PrintHello(void *threadarg)
{
struct thread_data *my_data;. my_data = (struct thread_data *)threadarg; taskid = my_data->thread_id; sum = my_data->sum; hello_msg = my_data->message; . }int main (int argc, char *argv[])
{... thread_data_array[t].thread_id = t; thread_data_array[t].sum = sum; thread_data_array[t].message = messages[t]; rc =pthread_create(&threads[t], NULL, PrintHello,(void*) &thread_data_array[t]); . }Copy the code
// This example demonstrates passing parameters incorrectly. The loop changes the contents of the address passed to the thread before it accesses the passed parameters.
int rc, t;
for(t=0; t<NUM_THREADS; t++)
{
printf("Creating thread %d\n", t);
rc = pthread_create(&threads[t], NULL, PrintHello,(void*) &t); . }Copy the code
Gets the current thread ID
#include<pthread.h>
pthread_t pthread_self(void);
Copy the code
The thread ID is of type pthread_t, which is unique in the current process, but has different implementations on different systems. It can be an integer value, a structure, or something you’d never guess.
Determine whether two threads are equal
#include<pthread.h>
int pthread_self(pthread_t t1,pthread_t t2);
Copy the code
Note that the thread ID objects in both functions are opaque and not easily examined. Because thread ids are opaque objects, the C == operator cannot be used to compare two thread ids.
Connecting and Detaching threads
pthread_join(threadid,status)
pthread_detach(threadid,status)
pthread_attr_setdetachstate(attr,detachstate)
pthread_attr_getdetachstate(attr,detachstate)
Copy the code
pthread_joinThe () function blocks the calling thread until the thread specified by threaDID terminates. If called in the target threadpthread_exit(), the programmer can obtain the destination thread termination status in the main thread. Connection threads can only be usedpthread_join() Connect once. A logic error occurs if you call it more than once. Two synchronization methods, mutexes and condition variables, are discussed later.Copy the code
Joinable or Not?
When a thread is created, it has a property that defines whether it is joinable or detached. Only the threads that are joined can be joined. If the created threads are separate, they cannot be joined. The final draft of the POSIX standard specifies that threads must be created to be joinable. However, not all implementations follow this convention. usepthread_createThe attr argument to () can explicitly create joinable or detached threadsCopy the code
Typical four steps are as follows:
Declare apthread_attr_tData type thread attribute variablepthread_attr_init() initializes the property variablepthread_attr_setdetachstate() after setting the separable state property, use thepthread_attr_destroy() Releases library resources occupied by attributesCopy the code
Detaching:
Pthread_detach () can be explicitly used to detach threads, although it is concatenable when created. There is no function opposite to the pthread_detach() function
It’s time again to demonstrate thread safety
// This example demonstrates using the Pthread join function to wait for a thread to terminate.
// Because some implementations do not make the thread of creation connectable by default, the example explicitly makes it connectable.
#include <pthread.h>
#include <stdio.h>
#define NUM_THREADS 3
void *BusyWork(void *null)
{
int i;
double result=0.0;
for (i=0; i<1000000; i++)
{
result = result + (double)random(a); }printf("result = %e\n",result);
pthread_exit((void *) 0);
}
int main (int argc, char *argv[])
{
pthread_t thread[NUM_THREADS];
pthread_attr_t attr;
int rc, t;
void *status;
/* Initialize and set thread detached attribute */
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
for(t=0; t<NUM_THREADS; t++)
{
printf("Creating thread %d\n", t);
rc = pthread_create(&thread[t], &attr, BusyWork, NULL);
if (rc)
{
printf("ERROR; return code from pthread_create() is %d/n", rc);
exit(- 1); }}/* Free attribute and wait for the other threads */
pthread_attr_destroy(&attr);
for(t=0; t<NUM_THREADS; t++)
{
rc = pthread_join(thread[t], &status);
if (rc)
{
printf("ERROR; return code from pthread_join() is %d\n", rc);
exit(- 1);
}
printf("Completed join with thread %d status= %ld\n",t, (long)status);
}
pthread_exit(NULL);
}
Copy the code
When a thread is set to split, if the thread is running very fast, it may terminate before the pthread_create() function returns. Since a thread can transfer its thread number and system resources to another thread after termination, using the thread number obtained by pthread_cretae() will cause an error.
Thread attributes
In Linux, thread attributes can be set based on actual project needs. Previously, we discussed the default properties of threads, which already solve most of the requirements of thread development. If you want higher performance, you need to manually configure the thread properties.
typedef struct
{
int detachstate; // Separate state of the thread
int schedpolicy; // Thread scheduling policy
struct sched schedparam;// Scheduling parameters for the thread
int inheritsched; // Thread inheritance
int scope; // Scope of the thread
size_t guardsize; // Alert buffer size at the end of the thread stack
int stackaddr_set; // Set the thread stack
void* stackaddr; // Start position of thread stack
size_t stacksize; // Thread stack size
}pthread_attr_t;
// We can see the related parameters in this structure above
Copy the code
The default properties are unbound, undetached, default stack, and the same level of priority as the parent process.
General routines for setting thread properties:
First: Define and initialize attribute variablespthread_attr_t
pthread_attr_init(a)Second: call the interface function for the property you want to setpthread_attr_setxxxxxxxx(a)Third: use this property for the second argument when creating the thread. Fourth: Destroy the propertypthread_destroy(a);
Copy the code
The mutex
Why mutex exists
Let’s do a little experiment. Two thread counts. If it adds up to 200,000 you don’t have to look down.
#include<pthread.h>
#include<unistd.h>
#include<stdio.h>
int count = 0;// Declare a global variable, we'll see it later
void *run(void *arg)
{
int i = 0;
for(i = 0; i <100000; i++)
{
count++;
printf("Count:%d\n",count);
usleep(2);
}
return (void*)0;
}
int main(int argc,char **argv)
{
pthread_t tid1,tid2;
int err1,err2;
err1 = pthread_create(&tid1,NULL,run,NULL);
err2 = pthread_create(&tid2,NULL,run,NULL);
if(err1==0 && err2==0)// Both threads were created successfully
{
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
}
return 0;
}
Copy the code
Okay, so why synchronize the threads
Forget it. We’re gonna have to make it official
(1) Multiple threads can operate on shared resources. (2) Threads may not operate on shared resources in the order in which they operate. (3) Processors generally do not operate on memory atomically
Mutex primitives
pthread_mutex_t mutex = PTHREAD_MUREX_INITALIZER // Used to initialize mutex
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr); // Initialize the lock.
// Initialize a mutex (mutex) -- > initial value can be seen as 1
int pthread_mutex_destroy(pthread_mutex_t *mutex); / / destruction of the lock
int pthread_mutex_lock(pthread_mutex_t *mutex); / / lock
int pthread_mutex_unlok(pthread_mutex_t *mutex); / / unlock
int pthread_mutex_trylock(pthread_mutex_t *mutex); // Try locking
Copy the code
Parameter definition
< here only define that init>
Parameter 1: an outgoing parameter that should be passed &mutex RESTRICT keyword: this is only used to restrict the pointer. It tells the compiler that all operations that change the pointer to the contents of the memory can be performed only by this pointer. Cannot be modified by a variable or pointer other than this pointer.
Parameter 2: mutually exclusive property. Is an passed argument, usually NULL, using the default property (shared between threads). Static initialization: If the mutex is statically allocated (defined globally, or modified with the static keyword), it can be initialized directly using macros. pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; Dynamic initialization: Local variables should be dynamically initialized. pthread_mutex_init(&mutex, NULL);
The attr object is used to set the attributes of the mutex object. It must be declared as pthread_mutextattr_t. The default value can be NULL. The Pthreads standard defines three optional mutex attributes: Protocol: specifies the Protocol used to prevent the mutex priority from changing. Prioceiling: Specifies the mutex priority upper limit. Process-shared: specifies the Process to share the mutex
Note that all implementations provide these three optional mutex attributes.
Q: There are multiple threads waiting for the same locked mutex. When the mutex is unlocked, which thread is the first to lock the mutex? A: Unless the thread uses A priority scheduling mechanism, the thread will be allocated by the system scheduler, and that thread will be the first to lock the mutex is random.Copy the code
Mutex usage
#include<pthread.h>
#include<unistd.h>
#include<stdio.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int count = 0;
void *run(void *arg)
{
int i = 0;
for(i = 0; i <100000; i++)
{
pthread_mutex_lock(&mutex);
count++;
pthread_mutex_unlock(&mutex);
printf("Count:%d\n",count);
usleep(2);
}
return (void*)0;
}
int main(int argc,char **argv)
{
pthread_t tid1,tid2;
int err1,err2;
err1 = pthread_create(&tid1,NULL,run,NULL);
err2 = pthread_create(&tid2,NULL,run,NULL);
if(err1==0 && err2==0)
{
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
}
return 0;
}
Copy the code
Take it and execute it. If it’s not 200,000, you don’t have to look down.
A deadlock
(The last progress · Family Bucket devoted a lot of space to this issue.)
Why I want to emphasize that lock and unlock must be written together, is to prevent human error resulting in deadlock deadlock, can not be unlocked. Either you forget to unlock it, and no one else can use it, or several threads are choking on key data so that no one can complete the task, and no one can unlock it. In this case, only destroy the cheapest lock and let the mission continue, but remember to restart the destroyed mission later.
Lock of
Optimistic locking
Optimistic locks, as you can see from their name, keep things simple. They assume that resources and data will not be changed, so they will not be locked, but optimistic locks will determine whether the current data has been changed when writing. Mechanisms such as version numbers can be used.
Optimistic locking is suitable for multiple application types to improve throughput.
Use self-growing integers to represent the data version number:
If the two characters do not interfere with each other, the male takes out and the version number is 0, the male writes and the version number is +1; If she writes, version number 1, if she writes, version number 2.
If they interfere with each other, the man picks out version 0; The male hasn’t written it yet, the female has taken it out, version number is 0; Male write, version number 1; If the version number does not match, the write fails and the amount and version number should be read again.
Alternatively, this can be done by timestamp
Pessimistic locking
Pessimistic lock is a kind of negative thinking, it always think the worst may appear, it was assumed that the data could be modified by others, so pessimistic locks in holding data always lock the resources, or data, so that other threads want to request this resource will be blocked, until when pessimistic locks release resources. Traditional relational database inside used a lot of this locking mechanism, such as row lock, table lock, read lock, write lock, etc., are in the operation before the first lock. The realization of pessimistic locking often depends on the locking function of database itself.
Implement database locks and so on.
Optimistic locks vs. pessimistic locks
Let’s just say, each to his own.
Optimistic locking works when there are few writes, i.e. conflicts are really rare, which can eliminate the overhead of locking and increase the overall throughput of the system. However, if there is a lot of conflict, this can actually degrade performance, so pessimistic locking is appropriate in this case.
Pessimistic locking can lead to long database access times and poor concurrency, especially for long transactions. Optimistic locking is often used in reality.
Spin locks &&mutex
Spinlocks and mutexes have always been used, but they used to be simply called locks. I didn’t know she had a name.
Wait () do you know? Timewait ()
Mutex: block wait for spin lock: Wait twice to ask: Ready? I’m in a hurry! Ready? Hurry up… hahahaha
Spinlocks principle is simple, if the thread holding the lock can lock is released in a short time resources, and the thread lock wait for competition there is no need to do between kernel mode and user mode switch into the blocking state, they just need to wait for a while (spin), until the thread holding the lock lock is released after available, thus avoiding the user process and the consumption of the kernel switch.
Because they avoid operating system process scheduling and thread switching, spin locks are usually appropriate for short periods of time. For this reason, operating system kernels often use spin locks. However, if locked for long periods of time, spin locks can be very costly in performance, preventing other threads from running and scheduling. The longer a thread holds the lock, the greater the risk that the thread holding the lock will be interrupted by the Operating System (OS) scheduler. If an interrupt occurs, the other threads will remain spinning (repeatedly trying to acquire the lock), and the thread holding the lock does not intend to release it, resulting in an indefinite delay until the thread holding the lock can complete and release it.
A good way to solve this problem is to set a spin time for the spin lock and release the spin lock immediately. Adaptive spin locking means that the spin time is not fixed, but is determined by the previous spin time on the same lock and the state the lock has. It is generally considered that the optimal time for a thread context switch is the time.
Condition variables,
- Condition variables provide another way to synchronize. Muexes enable synchronization by controlling access to data, while condition variables allow synchronization based on actual data values.
- Without the condition variable, the programmer must use threads to poll (perhaps in critical sections) to see if the condition is met. This is more resource-intensive because the threads are busy working continuously. Condition variables are one way to implement this polling.
- Conditional variables are often used with mutexes
The representative order for using conditional variables is as follows:
Conditional variable primitives
// Initialize the condition variable:
// I prefer static initialization to save trouble
pthread_cont_t cont = PTHREAD_COND_INITIALIZER;
// Let's look at dynamic initialization
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
Cond: used to receive a pipe condition variable that successfully initialized
//attr: usually NULL and ignored
// If there is initialization, there must be destruction
int pthread_cond_destroy(pthread_cond_t *cond);
// Since condition variables are used for waiting, it is important to see what is special about waiting
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex); // Wait unconditionally
int pthread_cond_timedwait(pthread_cond_t *cond,pthread_mutex_t mytex,const struct timespec *abstime); // Time wait
// Ok, join the queue waiting to wake up, that depends on how to wake up
int pthread_cond_signal(pthread_cond_t *cptr); // Wake up a thread waiting for the condition. There are multiple threads that wake up one in the order in which they are queued
int pthread_cond_broadcast(pthread_cond_t * cptr); // broadcast to wake up all and waiting threads
Copy the code
Condition variables and mutex
In a common thread pool used in server programming, multiple threads operate on the same task queue. Once a new task is found in the task queue, the child thread will fetch the task. Here, because it is multithreaded, it must involve protecting the task queue with a mutex (otherwise one of the threads manipulates the task queue, and in the middle of fetching the thread, the thread switches and retrieves the same task). But an obvious disadvantage of a mutex is that it has only two states: locked and unlocked. Imagine each thread constantly having to lock the task queue, check if there are new tasks in the task queue, acquire new tasks (with new tasks) or do nothing (with no new tasks), and release the lock in order to acquire new tasks. This would be very resource-intensive.
Condition variables complement mutex by allowing a thread to block and wait for another thread to send a signal. They are often used in conjunction with mutex. When used, condition variables are used to block a thread, and when the condition is not met, the thread tends to unlock the corresponding mutex and wait for the condition to change. If one of the other threads changes the condition variable, it tells the corresponding condition variable to wake up one or more threads that are blocked by the condition variable. These threads will re-lock the mutex and retest whether the condition is met. In general, condition variables are used for synchronization between threads. Corresponding to the thread pool scenario, we can have threads in a wait state, notify (one or more) when the main thread puts a new task on the work queue, and the notified thread reobtains the lock, acquires the task, and performs the related operation.
Matters needing attention
(1) Wake up must be protected by a mutex, otherwise wake up may occur before the condition variable is locked, resulting in a deadlock. (2) The order in which all threads on the condition variable are awakened to block is determined by the scheduling policy. (3) If no threads are blocked on the scheduling queue, the awakening will have no effect. (4) I used to like radio when I didn’t know anything. The pthread_cond_broadcast function must be used with care because it wakes up all threads that are blocking on a condition variable and will once again compete for the corresponding mutex.
False wake up and lost wake up
⑴ False awakening
On multi-core processors, pthread_cond_signal may activate more than one thread (blocking the thread on the condition variable). As a result, when one thread calls pthread_cond_signal(), multiple threads that called pthread_cond_wait() or pthread_cond_timedwait() return. This effect is called spurious wakeup.
Linux help has why not to fix it, it is not cost-effective.
So the usual standard solution is this:
⑵ Wake up lost
Either mode of waiting must be paired with a mutex to prevent multiple threads from disturbing it. The mutex must be normal or adaptive and must be locked by the local thread before entering pthread_cond_wait. Mutex must remain locked until the wait queue is updated. Unlock the thread before it enters the hang and waits. Lock the pthread_cond_wait until the condition is satisfied and the pthread_cond_wait is left. To restore its state before entering cont_wait.
Why is waiting locked?
To avoid wake loss problems. Here's a great explanation for whether to watch: HTTPS://stackoverflow.com/questions/4544234/calling-pthread-cond-signal-without-locking-mutexDo the whole thing, source code also put here: HTTPS://code.woboq.org/userspace/glibc/nptl/pthread_cond_wait.c.html
Copy the code
Add thread to wake queue to unlock thread. Ensures that a thread is atomic between the time it is in wait and the time it is added to the wake queue. But this atomicity depends on one prerequisite: the wake-er must also lock the same MUtex before calling the pthread_cond_broadcast or pthread_cond_signal to wake up the wait. If the above conditions are met, if A wait event A occurs before the wake event B, then A also obtains mutex before B, then B cannot enter the wake call before A is added to the wake queue, so B must be able to wake up A. For example, if there is no MUtex to synchronize between A and B, although B occurs after A, A may not be added to the wake queue when B wakes up, which is called wake loss.
Calling the pthread_cond_signal or pthread_cond_broadcast functions when the thread has not acquired the corresponding mutex can cause wake loss problems. Wake loss usually occurs when a thread calls the pthread_cond_signal or pthread_cond_broadcast function; Another thread is between testing the condition variable and calling the pthread_cond_wait function; No thread is in a blocking wait state.Copy the code
Use condition variables
// This example demonstrates several functions using the Pthreads conditional variable. The main program created three threads, two threads working, root "count" variable. The third thread waits for the count variable to reach the specified value.
#include <pthread.h>
#include <stdio.h>
#define NUM_THREADS 3
#define TCOUNT 10
#define COUNT_LIMIT 12
int count = 0;
int thread_ids[3] = {0.1.2};
pthread_mutex_t count_mutex;
pthread_cond_t count_threshold_cv;
void *inc_count(void *idp)
{
int j,i;
double result=0.0;
int *my_id = idp;
for(i=0; i<TCOUNT; i++) {
pthread_mutex_lock(&count_mutex);
count++;
/* Check the value of count and signal waiting thread when condition is reached. Note that this occurs while mutex is locked. */
if (count == COUNT_LIMIT) {
pthread_cond_signal(&count_threshold_cv);
printf("inc_count(): thread %d, count = %d Threshold reached./n",*my_id, count);
}
printf("inc_count(): thread %d, count = %d, unlocking mutex/n",*my_id, count);
pthread_mutex_unlock(&count_mutex);
/* Do some work so threads can alternate on mutex lock */
for (j=0; j<1000; j++)
result = result + (double)random(a); }pthread_exit(NULL);
}
void *watch_count(void *idp)
{
int *my_id = idp;
printf("Starting watch_count(): thread %d/n", *my_id);
/* Lock mutex and wait for signal. Note that the pthread_cond_wait routine will automatically and atomically unlock mutex while it waits. Also, note that if COUNT_LIMIT is reached before this routine is run by the waiting thread, the loop will be skipped to prevent pthread_cond_wait \ from never returning. */
pthread_mutex_lock(&count_mutex);
if (count<COUNT_LIMIT) {
pthread_cond_wait(&count_threshold_cv, &count_mutex);
printf("watch_count(): thread %d Condition signal received./n", *my_id);
}
pthread_mutex_unlock(&count_mutex);
pthread_exit(NULL);
}
int main(int argc, char *argv[])
{
int i, rc;
pthread_t threads[3];
pthread_attr_t attr;
/* Initialize mutex and condition variable objects */
pthread_mutex_init(&count_mutex, NULL);
pthread_cond_init (&count_threshold_cv, NULL);
/* For portability, explicitly create threads in a joinable state */
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
pthread_create(&threads[0], &attr, inc_count, (void *)&thread_ids[0]);
pthread_create(&threads[1], &attr, inc_count, (void *)&thread_ids[1]);
pthread_create(&threads[2], &attr, watch_count, (void *)&thread_ids[2]);
/* Wait for all threads to complete */
for (i=0; i<NUM_THREADS; i++) {
pthread_join(threads[i], NULL);
}
printf ("Main(): Waited on %d threads. Done./n", NUM_THREADS);
/* Clean up and exit */
pthread_attr_destroy(&attr);
pthread_mutex_destroy(&count_mutex);
pthread_cond_destroy(&count_threshold_cv);
pthread_exit(NULL);
}
Copy the code
The thread pool
The thread pool
Set pieces
Pthread API functions
Object creation in multithreading
For object construction to be thread-safe, there is only one requirement: do not expose yourself, i.e. do not expose the this pointer. That is to do the following:
Do not register any callbacks in constructorsthisPassing to a cross-thread object does not work even on the last line of the constructorCopy the code
For the first point, if a callback function is required to construct, then the two-step construct is used. Construct first and then call the callback function. For the third one, what if this class is a base class? It’s constructed not really constructed, there are subclasses and so on.
The reason for this design (passing this to subclasses is a different matter) is to prevent the construction process from being interrupted, creating a half-finished product.
Object destruction and race conditions
Object destruction, in multithreading, is complicated by the existence of races. Here’s an example:
Foo::~Foo() {/ / lock
/ / destructor
/ / unlock
}
void Foo::update(a){
/ / lock
// Data manipulation
/ / unlock
}
extern Foo *f;// Share resourcesA Process Operationsdelete f;
f = NULL; B Process Operationsif(f)
{
f->update(a); }Copy the code
This would be an awkward situation: WHEN A does the destructor, it already has the lock, but B passes F’s judgment because the pointer is still alive at that point and then gets stuck in the lock. What happens next? I don’t know, because the object takes the lock with it when it destructs… (The lock belongs to the object, object destructor, lock can not escape)
So what? Don’t worry, check out the blog: Smart Pointers
You can’t tell if a dynamically created object is still valid by looking at a pointer. A pointer is just pointing to a block of memory that, if it’s been destroyed, can’t be accessed.
shared_ptr/weak_ptr
Shared_ptr is a reference – counting smart pointer that is included in the C11 library. Shared_ptr is a class template that takes only one parameter and is easy to use.
Shared_str is a strong reference. As long as a shared_ptr pointing to an X object exists, the object will not be destructed. Weak_ptr is a weak reference, it does not control the life cycle of the object, but it knows if the object still exists. If the object exists, it can be upgraded to shared_ptr.
It’s better to give an example than to say so much:
class Observer{
private:
std::vector<weak_ptr<Observer>> vwo; // Use it like this
}
Copy the code
More on C++ memory security
The possible memory problems in C++ have several general aspects
- Buffer overflow
- Null dangling pointer/wild pointer
- Repeated release
- A memory leak
- Unpaired new[]/delete
- Memory fragments
Corresponding solutions:
- std::vetor
- shared_ptr/weak_ptr
- Scoped_ptr, only released once during object destruction
- scoped_ptr
- std::vetor
Resources to recommend
There are many Programing with POSIX Thread.