Multi-threaded programming (Linux C)

Multi-threaded programming can be said that every programmer's basic skills, but also one of the difficulties in development, this paper Linux C, for example, about the creation of the common threads and several threads in a synchronized manner, and finally multi-threaded programming was summary and think and give code examples.

First, create a thread

The first step in multithreaded programming, creating a thread. Creating a thread is actually adds a control process so that the same process multiple concurrent or parallel execution control flow.

Thread creation function, other functions are no longer listed here, you can reference pthread.h.

#include<pthread.h>

int pthread_create(
    pthread_t *restrict thread,  /*线程id*/
	const pthread_attr_t *restrict attr,    /*线程属性,默认可置为NULL,表示线程属性取缺省值*/
	void *(*start_routine)(void*),  /*线程入口函数*/ 
	void *restrict arg  /*线程入口函数的参数*/
	);
复制代码

Code Example:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>

char* thread_func1(void* arg) {
    pid_t pid = getpid();
    pthread_t tid = pthread_self();
    printf("%s pid: %u, tid: %u (0x%x)\n", (char*)arg, (unsigned int)pid, (unsigned int)tid, (unsigned int)tid);

    char* msg = "thread_func1";
    return msg;
}

void* thread_func2(void* arg) {
    pid_t pid = getpid();
    pthread_t tid = pthread_self();
    printf("%s pid: %u, tid: %u (0x%x)\n", (char*)arg, (unsigned int)pid, (unsigned int)tid, (unsigned int)tid);
    char* msg = "thread_func2 ";
    while(1) {
        printf("%s running\n", msg);
        sleep(1);
    }
    return NULL;
}

int main() {
    pthread_t tid1, tid2;
    if (pthread_create(&tid1, NULL, (void*)thread_func1, "new thread:") != 0) {
        printf("pthread_create error.");
        exit(EXIT_FAILURE);
    }

    if (pthread_create(&tid2, NULL, (void*)thread_func2, "new thread:") != 0) {
        printf("pthread_create error.");
        exit(EXIT_FAILURE);
    }
    pthread_detach(tid2);

    char* rev = NULL;
    pthread_join(tid1, (void *)&rev);
    printf("%s return.\n", rev);
    pthread_cancel(tid2);

    printf("main thread end.\n");
    return 0;
}
复制代码

Second, thread synchronization

Sometimes we need to cooperate with each other to execute multiple threads, then the need for inter-thread synchronization. Inter-thread synchronization method commonly used are:

  • Exclusive
  • signal
  • Condition variable

We look at an example of thread synchronization is not carried out:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>

#define LEN 100000
int num = 0;

void* thread_func(void* arg) {
    for (int i = 0; i< LEN; ++i) {
        num += 1;
    }
    
    return NULL;
}

int main() {
    pthread_t tid1, tid2;
    pthread_create(&tid1, NULL, (void*)thread_func, NULL);
    pthread_create(&tid2, NULL, (void*)thread_func, NULL);

    char* rev = NULL;
    pthread_join(tid1, (void *)&rev);
    pthread_join(tid2, (void *)&rev);

    printf("correct result=%d, wrong result=%d.\n", 2*LEN, num);
    return 0;
}
复制代码

The correct result=200000, wrong result=106860.result: .

[1] mutually exclusive

This is most easily understood when accessing critical resources, through mutually exclusive, limiting the same time can have at most one thread can acquire critical resources.

In fact, mutually exclusive logic is this: If you access the resources found in the street no other thread locked to unlocked, access to critical resources, if other threads during execution to find a locked mutex, the thread hangs waiting for unlock, access the current thread after critical resources, unlock and wake up any pending the mutex thread, waiting to be scheduled for execution again.

"Suspend wait" and Operation "wake up waiting threads" of how to achieve? Each Mutex there is a waiting queue, waiting for a thread to hang on Mutex, first in the queue to join their waiting, then set thread status is to sleep, and then call the scheduler function to switch to another thread. A thread to wake up other threads waiting in the queue, simply remove from waiting in a queue, to change its state from the sleep-ready, ready to join the queue, then the next time the scheduler function it is possible to switch to wake up execution the rout.

The main function is as follows:

#include <pthread.h>

int pthread_mutex_init(pthread_mutex_t *restrict mutex,     
       const pthread_mutexattr_t *restrict attr);       /*初始化互斥量*/
int pthread_mutex_destroy(pthread_mutex_t *mutex);      /*销毁互斥量*/
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
复制代码

Mutex to solve the above problem of erroneous results calculated, for example:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>

#define LEN 100000
int num = 0;

void* thread_func(void* arg) {
    pthread_mutex_t* p_mutex = (pthread_mutex_t*)arg;
    for (int i = 0; i< LEN; ++i) {
        pthread_mutex_lock(p_mutex);
        num += 1;
        pthread_mutex_unlock(p_mutex);
    }
    
    return NULL;
}

int main() {
    pthread_mutex_t m_mutex;
    pthread_mutex_init(&m_mutex, NULL);

    pthread_t tid1, tid2;
    pthread_create(&tid1, NULL, (void*)thread_func, (void*)&m_mutex);
    pthread_create(&tid2, NULL, (void*)thread_func, (void*)&m_mutex);

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

    pthread_mutex_destroy(&m_mutex);

    printf("correct result=%d, result=%d.\n", 2*LEN, num);
    return 0;
}
复制代码

operation result:correct result=200000, result=200000.

If you have nested there are other mutually exclusive mutually exclusive code, you need to pay attention to deadlock.

In both cases produce deadlock:

  • One case: if the same thread twice calls the lock, when the second call, because the lock is already occupied, the thread hangs waiting for the other thread releases the lock, but the lock of it is occupied with his own, the thread has been suspended and no chance to release the lock, and therefore would never wait in a suspended state, and deadlock.
  • Another typical deadlock situation is: A thread acquired the lock 1, Thread B gets a lock 2, then thread A calls trying to get lock lock 2, the result is the need to suspend the waiting thread releases the lock B 2, and then thread B also call lock trying to get a lock 1, the result is the need to suspend the release of the lock waiting for thread a 1, then thread a and B are always in a suspended state.

How to avoid deadlock:

  1. Without mutex (this often very difficult)
  2. Written procedures should be avoided while obtaining multiple locks.
  3. If we have a need to do so, there is a principle: all in the same order (commonly Mutex variable according to the order of address) to acquire a lock if all the threads needed multiple locks, no deadlock. ( Should such a program used when the lock 1, lock 2, the lock 3, they correspond to the address variable is locked Mutex 1 <lock 2 <lock 3, then all the threads need to obtain two or three lock press lock 1, lock 2, the lock of the order of 3 is obtained. If a sequence is difficult to determine all of the locks, should make use of the pthread_mutex_trylockcall instead of the pthread_mutex_lockcall, to avoid deadlock. )
[2] condition variable

Condition variables can be summed up: a thread to wait a condition exists (and this condition is determined by other threads) to continue down, and now this condition is not satisfied, the thread blocked waiting until the other threads in the implementation process manipulation conditions are established, they wake up the thread to continue.

Correlation function as follows:

#include <pthread.h>

int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_init(pthread_cond_t *restrict cond,
       const pthread_condattr_t *restrict attr);
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
       pthread_mutex_t *restrict mutex,
       const struct timespec *restrict abstime);
int pthread_cond_wait(pthread_cond_t *restrict cond,
       pthread_mutex_t *restrict mutex);
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
复制代码

For most easily understood condition variables example, "producer - consumer" mode, to send data to the producer thread queue consumer thread take data from the queue, when the process speed is greater than the producer consumer thread thread will produce no data in the queue an approach is to wait for some time again "poll", but this approach is not very good, and so you do not know how long this time the condition variable can be a good solution to this problem . Here is the code:

#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<pthread.h>
#include<errno.h>
#include<string.h>

#define LIMIT 1000

struct data {
    int n;
    struct data* next;
};

pthread_cond_t condv = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mlock = PTHREAD_MUTEX_INITIALIZER; 
struct data* phead = NULL;

void producer(void* arg) {
    printf("producer thread running.\n");
    int count = 0;
    for (;;) {
        int n = rand() % 100;
        struct data* nd = (struct data*)malloc(sizeof(struct data));
        nd->n = n;

        pthread_mutex_lock(&mlock);
        struct data* tmp = phead;
        phead = nd;
        nd->next = tmp;
        pthread_mutex_unlock(&mlock);
        pthread_cond_signal(&condv);

        count += n;

        if(count > LIMIT) {
            break;
        }
        sleep(rand()%5);
    }
    printf("producer count=%d\n", count);
}

void consumer(void* arg) {
    printf("consumer thread running.\n");
    int count = 0;
    for(;;) {
        pthread_mutex_lock(&mlock);
        if (NULL == phead) {
            pthread_cond_wait(&condv, &mlock);
        } else {
            while(phead != NULL) {
                count += phead->n;
                struct data* tmp = phead;
                phead = phead->next;
                free(tmp);
            }
        }
        pthread_mutex_unlock(&mlock);
        if (count > LIMIT)
            break;
    }
    printf("consumer count=%d\n", count);
}

int main() {
    pthread_t tid1, tid2;
    pthread_create(&tid1, NULL, (void*)producer, NULL);
    pthread_create(&tid2, NULL, (void*)consumer, NULL);
    
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

    return 0;
}
复制代码

Execution logic condition variables:

The key is to understand the implementation of int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex)what happens when here, others are relatively easy to understand. Before performing this function needs to acquire the mutex to determine whether the conditions are met, if the conditions are fulfilled, continue after the release of the lock-down; if the judgment is not satisfied execution condition, then release the lock, the thread back up here, wait until the other thread notifies conditions are fulfilled, awakened thread lock again, after performing down to release the lock. ( Short: the lock is released -> block waiting for -> lock to return after waking up )

Implementation details to see the source code pthread_cond_wait.c and pthread_cond_signal.c

The above example can be complicated, the following sample code is more concise:

#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<pthread.h>
#include<errno.h>
#include<string.h>

#define NUM 3
pthread_cond_t condv = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mlock = PTHREAD_MUTEX_INITIALIZER; 

void producer(void* arg) {
    int n = NUM;
    while(n--) {
        sleep(1);
        pthread_cond_signal(&condv);
        printf("producer thread send notify signal. %d\t", NUM-n);
    }
}

void consumer(void* arg) {
    int n = 0;
    while (1) {
        pthread_cond_wait(&condv, &mlock);
        printf("recv producer thread notify signal. %d\n", ++n);
        if (NUM == n) {
            break;
        }
    }
}

int main() {
    pthread_t tid1, tid2;
    pthread_create(&tid1, NULL, (void*)producer, NULL);
    pthread_create(&tid2, NULL, (void*)consumer, NULL);
    
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

    return 0;
}
复制代码

operation result:

producer thread send notify signal. 1   recv producer thread notify signal. 1
producer thread send notify signal. 2   recv producer thread notify signal. 2
producer thread send notify signal. 3   recv producer thread notify signal. 3
复制代码

[3] Semaphore

Semaphore is suitable for controlling a shared resource supports only a limited number of users. For maintaining a count value between 0 and the maximum value specified. When the thread finishes once semaphorethe object is waiting, the counter value minus one; when the thread to complete a semaphorerelease object, a count is incremented. When the count value is 0, the thread is suspended to wait, until the count value exceeds 0.

The main function is as follows:

#include <semaphore.h>

int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_post(sem_t * sem);
int sem_destroy(sem_t * sem);
复制代码

Code examples are as follows:

#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<pthread.h>
#include<errno.h>
#include<string.h>
#include<semaphore.h>

#define NUM 5

int queue[NUM];
sem_t psem, csem; 

void producer(void* arg) {
    int pos = 0;
    int num, count = 0;
    for (int i=0; i<12; ++i) {
        num = rand() % 100;
        count += num;
        sem_wait(&psem);
        queue[pos] = num;
        sem_post(&csem);
        printf("producer: %d\n", num); 
        pos = (pos+1) % NUM;
        sleep(rand()%2);
    }
    printf("producer count=%d\n", count);
}

void consumer(void* arg){
    int pos = 0;
    int num, count = 0;
    for (int i=0; i<12; ++i) {
        sem_wait(&csem);
        num = queue[pos];
        sem_post(&psem);
        printf("consumer: %d\n", num);
        count += num;
        pos = (pos+1) % NUM;
        sleep(rand()%3);
    }
    printf("consumer count=%d\n", count);    
} 

int main() {
    sem_init(&psem, 0, NUM);
    sem_init(&csem, 0, 0);

    pthread_t tid[2];
    pthread_create(&tid[0], NULL, (void*)producer, NULL);
    pthread_create(&tid[1], NULL, (void*)consumer, NULL);
    pthread_join(tid[0], NULL);
    pthread_join(tid[1], NULL);
    sem_destroy(&psem);
    sem_destroy(&csem);

    return 0;
}
复制代码

Semaphore execution logic:

When the need to acquire a shared resource, the semaphore check, if the value is greater than 0, the value by one, access to shared resources, after access, value plus 1, if there is pending for the semaphore threads, wherein a wakeup thread; check if the signal is 0, then suspended to wait.

Refer to the source sem_post.c

Third, multi-threaded programming Summary and Thinking

Finally, we summarize and thinking of multi-threaded programming.

  • The first point is that we must pay attention to consider the issue during the synchronization of multithreaded programming, because in most cases we have created multiple threads is to get them to work together, if not synchronized, problems may occur.
  • Second, the problem of deadlock. When multiple threads access multiple critical resources, improper handling deadlock ensues. If you encounter a compiler, runtime stuck, there may be a deadlock occurs, you can think about those multiple threads access critical resources, so find the problem faster.
  • Third, the critical resource processing, multi-threaded problem, a big problem because when multiple threads access to critical resources, access and recycled in a manner that will deal with all the critical resources into a thread, with the service request thread other threads, so that only one thread access to critical resources will solve many problems.
  • Fourth, the thread pool, when dealing with a large number of short tasks, we can create a good thread pool, thread pool thread continues to take the task execution from the task queue, so do not create a large number of threads and destroying threads, is no longer here detailing.

Reference document: pthread.h - Threads

Welcome attention to personal micro-channel public number, Let's go!

Here Insert Picture Description

Guess you like

Origin juejin.im/post/5d15ef19f265da1b8811efee