Linux multithreading development

Linux multithreading development

thread

Thread overview

  • Like a process, a thread is a mechanism that allows an application to execute multiple tasks concurrently. A process can contain multiple threads. All threads in the same program will execute the same program independently and share the same global memory area, including initialized data segment, uninitialized data segment and heap memory segment. (UNIX process in the traditional sense is only a special case of multithreaded program, which contains only one thread)
  • Process is the smallest unit of CPU allocated resources, and thread is the smallest unit of operating system scheduling execution.
  • A thread is a lightweight process (LWP: Light Weight Process). In the Linux environment, the essence of a thread is still a process.
  • View the LWP number of the specified process: ps – Lf pid

Difference between thread and process

  • Information between processes is difficult to share. Except for the read-only code segment, the parent-child processes do not share memory, so some inter process communication methods must be adopted to exchange information between processes.
  • The cost of calling fork() to create a process is relatively high. * * even if the write time replication technology is used, a variety of process attributes such as memory page table and file descriptor table still need to be copied. * * this means that the time cost of fork() call is still expensive.
  • Threads can easily and quickly share information. Simply copy the data to a shared (global or heap) variable.
  • Creating a thread is usually 10 times or more faster than creating a process. Threads share virtual address space, so there is no need to copy memory on write or page table.

Shared and unshared resources between threads

  • Shared resources (data in the kernel)

    • Process ID and parent process ID
    • Process group ID and session ID
    • User ID and user group ID
    • File descriptor table
    • Signal processing
    • File system related information: file permission mask (umask) and current working directory
    • Virtual address space (except stack,. text)
  • Unshared resources

    • Thread ID
    • Signal mask
    • Thread specific data
    • error variable
    • Real time scheduling policy and priority
    • Stack, local variables and function call link information

NPTL

  • When Linux was first developed, threads were not really supported in the kernel. But it does make the process a schedulable entity through the clone() system call. This call creates a copy of the calling process, which shares the same address space as the calling process. The Linux threads project uses this call to simulate thread support in user space. Unfortunately, this method has some disadvantages, especially in signal processing, scheduling and inter process synchronization. In addition, this threading model does not meet the requirements of POSIX.
  • To improve Linux threads, you need kernel support and rewrite the thread library. Two competing projects began to meet these requirements. A team including IBM developers carried out the NGPT (next generation POSIX threads) project. At the same time, some Red Hat developers have launched the NPTL project. NGPT was abandoned in mid-2003, leaving the field entirely to NPTL.
  • NPTL, or Native POSIX Thread Library, is a new implementation of Linux threads. It overcomes the shortcomings of Linux threads and meets the requirements of POSIX. Compared with Linux threads, it provides significant improvements in performance and stability.
  • View the current pthread library version: getconf GNU_LIBPTHREAD_VERSION

Thread operation

Any thread can recycle other threads through pthread_join

man pthread

  • int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
  • pthread_t pthread_self(void);
  • int pthread_equal(pthread_t t1, pthread_t t2);
  • void pthread_exit(void *retval);
  • int pthread_join(pthread_t thread, void **retval);
  • int pthread_detach(pthread_t thread);
  • int pthread_cancel(pthread_t thread);

pthread_create.c

-pthread -lpthread

/*
    In general, the thread where the main function is located is called the main thread (main thread), and the other created threads
    It is called a child thread.
    By default, there is only one process in the program, which is called by fork() function and 2
    By default, there is only one thread in the program, pthread_create() function call, 2 threads.

    #include <pthread.h>
    int pthread_create(pthread_t *thread, const pthread_attr_t *attr, 
    void *(*start_routine) (void *), void *arg);

        - Function: create a child thread
        - Parameters:
            - thread: After the thread is successfully created, the thread ID of the child thread is written to this variable.
            - attr : Set the properties of the thread. Generally, the default value is NULL
            - start_routine : Function pointer. This function is the logical code that the child thread needs to process
            - arg : For the third parameter, pass the parameter
        - Return value:
            Success: 0
            Failed: error number returned. This error number is different from the previous errno.
            Get the information of the error number: char * strerror(int errnum);

*/
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>

void * callback(void * arg) {
    printf("child thread...\n");
    printf("arg value: %d\n", *(int *)arg);
    return NULL;
}

int main() {

    pthread_t tid;

    int num = 10;

    // Create a child thread
    int ret = pthread_create(&tid, NULL, callback, (void *)&num);

    if(ret != 0) {
        char * errstr = strerror(ret);
        printf("error : %s\n", errstr);
    } 

    for(int i = 0; i < 5; i++) {
        printf("%d\n", i);
    }

    sleep(1);

    return 0;   // exit(0);
}

pthread_exit.c

/*

    #include <pthread.h>
    void pthread_exit(void *retval);
        Function: terminate a thread, which thread is called, which terminates which thread.
        Parameters:
            retval:You need to pass a pointer as a return value in pthread_ Obtained in join().

    pthread_t pthread_self(void);
        Function: get the thread ID of the current thread

    int pthread_equal(pthread_t t1, pthread_t t2);
        Function: compare whether two thread ID s are equal
        Different operating systems, pthread_ The implementation of T type is different. Some are unsigned long integers, and some are unsigned long integers
        It is implemented using structures.
*/
#include <stdio.h>
#include <pthread.h>
#include <string.h>

void * callback(void * arg) {
    printf("child thread id : %ld\n", pthread_self());
    return NULL;    // pthread_exit(NULL);
} 

int main() {

    // Create a child thread
    pthread_t tid;
    int ret = pthread_create(&tid, NULL, callback, NULL);

    if(ret != 0) {
        char * errstr = strerror(ret);
        printf("error : %s\n", errstr);
    }

    // Main thread
    for(int i = 0; i < 5; i++) {
        printf("%d\n", i);
    }

    printf("tid : %ld, main thread id : %ld\n", tid ,pthread_self());

    // Let the main thread exit. When the main thread exits, it will not affect other normal running threads.
    pthread_exit(NULL);

    printf("main thread exit\n");

    return 0;   // exit(0);
}

pthread_join.c Connection terminated thread

About pthread_ The second parameter of join() is the second level pointer
That is, why is the parameter of a function a pointer type

/*
    #include <pthread.h>
    int pthread_join(pthread_t thread, void **retval);
        - Function: connect with a terminated thread
                Reclaim resources from child threads
                This function is a blocking function. Only one child thread can be recycled at a time
                It is generally used in the main thread
        - Parameters:
            - thread: ID of the child thread that needs to be recycled
            - retval: Receive the return value when the child thread exits
        - Return value:
            0 : success
            Non 0: failed, returned error number
*/

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

int value = 10;

void * callback(void * arg) {
    printf("child thread id : %ld\n", pthread_self());
    // sleep(3);
    // return NULL; 
    // int value = 10; //  local variable
    pthread_exit((void *)&value);   // return (void *)&value;
} 

int main() {

    // Create a child thread
    pthread_t tid;
    int ret = pthread_create(&tid, NULL, callback, NULL);

    if(ret != 0) {
        char * errstr = strerror(ret);
        printf("error : %s\n", errstr);
    }

    // Main thread
    for(int i = 0; i < 5; i++) {
        printf("%d\n", i);
    }

    printf("tid : %ld, main thread id : %ld\n", tid ,pthread_self());

    // The main thread calls pthread_join() reclaims the resources of the child thread
    int * thread_retval;
    ret = pthread_join(tid, (void **)&thread_retval); //What is passed is the pointer of the pointer, blocking

    if(ret != 0) {
        char * errstr = strerror(ret);
        printf("error : %s\n", errstr);
    }

    printf("exit data : %d\n", *thread_retval);

    printf("Recycling sub thread resources succeeded!\n");

    // Let the main thread exit. When the main thread exits, it will not affect other normal running threads.
    pthread_exit(NULL);

    return 0; 
}

pthread_detach.c

The thread allocates space from the process stack. The size is not fixed. If the allocated space is greater than the process stack space, a segment error occurs when running directly.

/*
    #include <pthread.h>
    int pthread_detach(pthread_t thread);
        - Function: separate a thread. When the separated thread terminates, it will automatically release resources and return them to the system.
          1.It cannot be separated multiple times, resulting in unpredictable behavior.
          2.If you cannot connect to a separated thread, an error will be reported.
        - Parameter: ID of the thread to be detached
        - Return value:
            Success: 0
            Failed: error number returned
*/
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>

void * callback(void * arg) {
    printf("chid thread id : %ld\n", pthread_self());
    return NULL;
}

int main() {

    // Create a child thread
    pthread_t tid;

    int ret = pthread_create(&tid, NULL, callback, NULL);
    if(ret != 0) {
        char * errstr = strerror(ret);
        printf("error1 : %s\n", errstr);
    }

    // Output the id of the main thread and the child thread
    printf("tid : %ld, main thread id : %ld\n", tid, pthread_self());

    // Set the sub thread separation. After the sub thread separation, the corresponding resources at the end of the sub thread do not need to be released by the main thread
    ret = pthread_detach(tid);
    if(ret != 0) {
        char * errstr = strerror(ret);
        printf("error2 : %s\n", errstr);
    }

    // After setting separation, connect pthread to the separated child threads_ join()
    // ret = pthread_join(tid, NULL);
    // if(ret != 0) {
    //     char * errstr = strerror(ret);
    //     printf("error3 : %s\n", errstr);
    // }

    pthread_exit(NULL);

    return 0;
}

pthread_cancle.c

/*
    #include <pthread.h>
    int pthread_cancel(pthread_t thread);
        - Function: cancel thread (let thread terminate)
            Canceling a thread can terminate the operation of a thread,
            However, it does not terminate immediately, but only when the child thread reaches a cancellation point.
            Cancellation point: some system calls specified by the system can be roughly understood as switching from user area to kernel area. This position is called cancellation point.
*/

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

void * callback(void * arg) {
    printf("chid thread id : %ld\n", pthread_self());
    for(int i = 0; i < 5; i++) {
        printf("child : %d\n", i); //Cancel point
    }
    return NULL;
}

int main() {
    
    // Create a child thread
    pthread_t tid;

    int ret = pthread_create(&tid, NULL, callback, NULL);
    if(ret != 0) {
        char * errstr = strerror(ret);
        printf("error1 : %s\n", errstr);
    }

    // Cancel thread
    pthread_cancel(tid);

    for(int i = 0; i < 5; i++) {
        printf("%d\n", i);
    }

    // Output the id of the main thread and the child thread
    printf("tid : %ld, main thread id : %ld\n", tid, pthread_self());

    
    pthread_exit(NULL);

    return 0;
}

Thread properties

  • Thread property type pthread_attr_t
  • int pthread_attr_init(pthread_attr_t *attr);
  • int pthread_attr_destroy(pthread_attr_t *attr);
  • int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
  • int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

pthread_attr.c

/*
    int pthread_attr_init(pthread_attr_t *attr);
        - Initialize thread property variables

    int pthread_attr_destroy(pthread_attr_t *attr);
        - Free resources for thread properties

    int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
        - Gets the status property of thread separation

    int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
        - Set the status properties of thread separation
*/     

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

void * callback(void * arg) {
    printf("chid thread id : %ld\n", pthread_self());
    return NULL;
}

int main() {

    // Create a thread property variable
    pthread_attr_t attr;
    // Initialize Attribute Variable
    pthread_attr_init(&attr);

    // set a property
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); //Default thread detached state property

    // Create a child thread
    pthread_t tid;

    int ret = pthread_create(&tid, &attr, callback, NULL);
    if(ret != 0) {
        char * errstr = strerror(ret);
        printf("error1 : %s\n", errstr);
    }

    // Gets the stack size of the thread
    size_t size;
    pthread_attr_getstacksize(&attr, &size);
    printf("thread stack size : %ld\n", size);

    // Output the id of the main thread and the child thread
    printf("tid : %ld, main thread id : %ld\n", tid, pthread_self());

    // Free thread property resources
    pthread_attr_destroy(&attr);

    pthread_exit(NULL);

    return 0;
}

Thread synchronization

  • The main advantage of threads is that they can share information through global variables. However, this convenient sharing comes at a price: you must ensure that multiple threads do not modify the same variable at the same time, or that a thread does not read variables being modified by other threads.
  • Critical area refers to the code fragment that accesses a shared resource, and the execution of this code shall be atomic operation, that is, other threads accessing the same shared resource at the same time shall not interrupt the execution of this fragment.
  • Thread synchronization: that is, when a thread is operating on memory, other threads cannot operate on the memory address until the thread completes the operation, other threads can operate on the memory address, while other threads are waiting.

sell_tickets.c

/*
    A case of using multithreading to buy tickets.
    There are three windows, a total of 100 tickets.
*/

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

// Global variable. All threads share this resource.
int tickets = 100;

void * sellticket(void * arg) {
    // Selling tickets
    while(tickets > 0) {
        usleep(6000); // Sleep 6000 microseconds
        printf("%ld Selling the third %d Tickets\n", pthread_self(), tickets);
        tickets--;
    }
    return NULL;
}

int main() {

    // Create 3 child threads
    pthread_t tid1, tid2, tid3;
    pthread_create(&tid1, NULL, sellticket, NULL);
    pthread_create(&tid2, NULL, sellticket, NULL);
    pthread_create(&tid3, NULL, sellticket, NULL);

    // Reclaim the resources of child threads and block
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    pthread_join(tid3, NULL);

    // Set thread separation.
    // pthread_detach(tid1);
    // pthread_detach(tid2);
    // pthread_detach(tid3);

    pthread_exit(NULL); // Exit main thread

    return 0;
}

mutex

  • **To avoid problems when threads update shared variables, mutex can be used to ensure that only one thread can access a shared resource at the same time** Mutex can be used to guarantee atomic access to any shared resource.

  • Mutexes have two states: locked and unlocked. At most one thread can lock the mutex at any time. Attempting to lock a locked mutex again may block the thread or fail to report an error, depending on the method used when locking.

  • Once a thread locks a mutex, it becomes the owner of the mutex. Only the owner can unlock the mutex. In general, different mutexes are used for each shared resource (which may be composed of multiple related variables). Each thread will use the following protocol when accessing the same resource:

    • Lock mutex for shared resources
    • access to shared resources
    • Unlock mutex
  • If multiple threads attempt to execute this piece of code (a critical area), in fact, only one thread can hold the mutex (other threads will be blocked), that is, only one thread can enter this code area at the same time, as shown in the following figure:

Mutex related operation function

  • Type of mutex pthread_mutex_t
  • 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.c

/*
    Type of mutex pthread_mutex_t
    int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
        - Initialize mutex
        - Parameters:
            - mutex :  Mutex variable to initialize
            - attr :  Mutex related property, NULL
        - restrict : C The modifier of a language, a modified pointer, cannot be operated by another pointer.
            pthread_mutex_t *restrict mutex = xxx;
            pthread_mutex_t *mutex1 = mutex;

    int pthread_mutex_destroy(pthread_mutex_t *mutex);
        - Release mutually exclusive resources

    int pthread_mutex_lock(pthread_mutex_t *mutex);
        - Locking is blocked. If one thread is locked, other threads can only block and wait

    int pthread_mutex_trylock(pthread_mutex_t *mutex);
        - Try locking. If locking fails, it will not block and will return directly.

    int pthread_mutex_unlock(pthread_mutex_t *mutex);
        - Unlock
*/
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

// Global variable. All threads share this resource.
int tickets = 1000;

// Create a mutex
pthread_mutex_t mutex;

void * sellticket(void * arg) {

    // Selling tickets
    while(1) {

        // Lock
        pthread_mutex_lock(&mutex);

        if(tickets > 0) {
            usleep(6000);
            printf("%ld Selling the third %d Tickets\n", pthread_self(), tickets);
            tickets--;
        }else {
            // Unlock
            pthread_mutex_unlock(&mutex);
            break;
        }

        // Unlock
        pthread_mutex_unlock(&mutex);
    }

    

    return NULL;
}

int main() {

    // Initialize mutex
    pthread_mutex_init(&mutex, NULL);

    // Create 3 child threads
    pthread_t tid1, tid2, tid3;
    pthread_create(&tid1, NULL, sellticket, NULL);
    pthread_create(&tid2, NULL, sellticket, NULL);
    pthread_create(&tid3, NULL, sellticket, NULL);

    // Reclaim the resources of child threads and block
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    pthread_join(tid3, NULL);

    pthread_exit(NULL); // Exit main thread

    // Release mutex resources
    pthread_mutex_destroy(&mutex);

    return 0;
}

deadlock

  • Sometimes, a thread needs to access two or more different shared resources at the same time, and each resource is managed by different mutexes. When more than one thread locks the same set of mutexes, deadlock may occur.
  • During the execution of two or more processes, a phenomenon of waiting for each other caused by competing for shared resources. If there is no external force, they will not be able to move forward. At this time, it is said that the system is in a deadlock state or the system has a deadlock.
  • Several scenarios of Deadlock:
    • Forget to release the lock
    • Repeat locking
    • Multi thread and multi lock, preempting lock resources

deadlock.c

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

// Global variable. All threads share this resource.
int tickets = 1000;

// Create a mutex
pthread_mutex_t mutex;

void * sellticket(void * arg) {

    // Selling tickets
    while(1) {

        // Lock
        pthread_mutex_lock(&mutex);
        pthread_mutex_lock(&mutex); //Repeat locking

        if(tickets > 0) {
            usleep(6000);
            printf("%ld Selling the third %d Tickets\n", pthread_self(), tickets);
            tickets--;
        }else {
            // Unlock
            pthread_mutex_unlock(&mutex);
            break;
        }

        // Unlock lock not released
        // pthread_mutex_unlock(&mutex);
        // pthread_mutex_unlock(&mutex);
    }

    return NULL;
}

int main() {

    // Initialize mutex
    pthread_mutex_init(&mutex, NULL);

    // Create 3 child threads
    pthread_t tid1, tid2, tid3;
    pthread_create(&tid1, NULL, sellticket, NULL);
    pthread_create(&tid2, NULL, sellticket, NULL);
    pthread_create(&tid3, NULL, sellticket, NULL);

    // Reclaim the resources of child threads and block
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    pthread_join(tid3, NULL);

    pthread_exit(NULL); // Exit main thread

    // Release mutex resources
    pthread_mutex_destroy(&mutex);

    return 0;
}

deadlock1.c

Multi thread and multi lock, preempting lock resources

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

// Create 2 mutexes
pthread_mutex_t mutex1, mutex2;

void * workA(void * arg) {

    pthread_mutex_lock(&mutex1);
    sleep(1);
    pthread_mutex_lock(&mutex2); //Wait for workB to unlock

    printf("workA....\n");

    pthread_mutex_unlock(&mutex2);
    pthread_mutex_unlock(&mutex1);
    return NULL;
}


void * workB(void * arg) {
    pthread_mutex_lock(&mutex2);
    sleep(1);
    pthread_mutex_lock(&mutex1); //Wait for workA to unlock

    printf("workB....\n");

    pthread_mutex_unlock(&mutex1);
    pthread_mutex_unlock(&mutex2);

    return NULL;
}

int main() {

    // Initialize mutex
    pthread_mutex_init(&mutex1, NULL);
    pthread_mutex_init(&mutex2, NULL);

    // Create 2 child threads
    pthread_t tid1, tid2;
    pthread_create(&tid1, NULL, workA, NULL);
    pthread_create(&tid2, NULL, workB, NULL);

    // Reclaim child thread resources
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

    // Release mutex resources
    pthread_mutex_destroy(&mutex1);
    pthread_mutex_destroy(&mutex2);

    return 0;
}

Read write lock

  • When a thread already holds a mutex, the mutex blocks all threads trying to enter the critical zone. However, consider a case where the thread currently holding the mutex only wants to read and access the shared resource, and several other threads also want to read the shared resource. However, due to the exclusivity of the mutex, all other threads cannot obtain the lock, so they cannot read and access the shared resource. In fact, multiple threads reading and accessing the shared resource at the same time will not cause a problem.
  • In the data reading and writing operations, there are more reading operations and less writing operations, such as the application of reading and writing database data. In order to meet the current requirement of allowing multiple reads but only one write, threads provide read-write locks.
  • Features of read-write lock:
    • If there are other threads reading data, other threads are allowed to perform read operations, but write operations are not allowed.
    • If other threads write data, other threads are not allowed to read or write.
    • Write is exclusive and has high priority.

Read / write lock related operation functions

  • Type of read / write lock pthread_rwlock_t
  • int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
  • int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
  • int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
  • int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
  • int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
  • int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
  • int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

rwlock.x

/*
    Type of read / write lock pthread_rwlock_t
    int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
    int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
    int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
    int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
    int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
    int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
    int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

    Case: 8 threads operate on the same global variable.
    3 One thread writes the global variable from time to time, and five threads read the global variable from time to time
*/

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

// Create a shared data
int num = 1;
// pthread_mutex_t mutex;
pthread_rwlock_t rwlock;

void * writeNum(void * arg) {

    while(1) {
        pthread_rwlock_wrlock(&rwlock);
        num++;
        printf("++write, tid : %ld, num : %d\n", pthread_self(), num);
        pthread_rwlock_unlock(&rwlock);
        usleep(100);
    }

    return NULL;
}

void * readNum(void * arg) {

    while(1) {
        pthread_rwlock_rdlock(&rwlock);
        printf("===read, tid : %ld, num : %d\n", pthread_self(), num);
        pthread_rwlock_unlock(&rwlock);
        usleep(100);
    }

    return NULL;
}

int main() {

   pthread_rwlock_init(&rwlock, NULL);

    // Create 3 write threads and 5 read threads
    pthread_t wtids[3], rtids[5];
    for(int i = 0; i < 3; i++) {
        pthread_create(&wtids[i], NULL, writeNum, NULL);
    }

    for(int i = 0; i < 5; i++) {
        pthread_create(&rtids[i], NULL, readNum, NULL);
    }

    // Set thread separation
    for(int i = 0; i < 3; i++) {
       pthread_detach(wtids[i]);
    }

    for(int i = 0; i < 5; i++) {
         pthread_detach(rtids[i]);
    }

    pthread_exit(NULL);

    pthread_rwlock_destroy(&rwlock);

    return 0;
}

Producer consumer model

prodcust.c

/*
    Producer consumer model (rough version)
*/
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>

// Create a mutex
pthread_mutex_t mutex;

struct Node{
    int num;
    struct Node *next;
};

// Head node
struct Node * head = NULL;

void * producer(void * arg) {

    // Constantly create new nodes and add them to the linked list
    while(1) {
        pthread_mutex_lock(&mutex);
        struct Node * newNode = (struct Node *)malloc(sizeof(struct Node));
        
        newNode->next = head; //Head insertion
        head = newNode;
        newNode->num = rand() % 1000;
        
        printf("add node, num : %d, tid : %ld\n", newNode->num, pthread_self());
        pthread_mutex_unlock(&mutex);
        usleep(100);
    }

    return NULL;
}

void * customer(void * arg) {

    while(1) {
        pthread_mutex_lock(&mutex);
        // Save pointer to header node
        struct Node * tmp = head;

        // Determine whether there is data
        if(head != NULL) {
            // Have data
            head = head->next;
            printf("del node, num : %d, tid : %ld\n", tmp->num, pthread_self());
            free(tmp);
            
            pthread_mutex_unlock(&mutex);
            usleep(100);
        } else {
            // no data
            pthread_mutex_unlock(&mutex);
        }
    }
    return  NULL;
}

int main() {

    pthread_mutex_init(&mutex, NULL);

    // Create 5 producer threads and 5 consumer threads
    pthread_t ptids[5], ctids[5];

    for(int i = 0; i < 5; i++) {
        pthread_create(&ptids[i], NULL, producer, NULL);
        pthread_create(&ctids[i], NULL, customer, NULL);
    }

    for(int i = 0; i < 5; i++) {
        pthread_detach(ptids[i]);
        pthread_detach(ctids[i]);
    }

    while(1) {
        sleep(10);
    }

    pthread_mutex_destroy(&mutex);

    pthread_exit(NULL);

    return 0;
}

compile
ulimit -a check to see if the core file is open
ulimit -c unlimited allows core files to be generated
gcc prodcust.c -o prodcust -pthread -g

gdb prodcust
core-file core

Conditional variable

  • Type of conditional variable pthread_cond_t
  • int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
  • int pthread_cond_destroy(pthread_cond_t *cond);
  • int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
  • int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
  • int pthread_cond_signal(pthread_cond_t *cond);
  • int pthread_cond_broadcast(pthread_cond_t *cond);

cond.c

/*
    Type of conditional variable pthread_cond_t
    int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
    int pthread_cond_destroy(pthread_cond_t *cond);
    int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
        - Wait. If this function is called, the thread will block.
    int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
        - How long to wait? After calling this function, the thread will block until the specified time ends.
    int pthread_cond_signal(pthread_cond_t *cond);
        - Wake up one or more waiting threads
    int pthread_cond_broadcast(pthread_cond_t *cond);
        - Wake up all waiting threads
*/
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>

// Create a mutex
pthread_mutex_t mutex;
// Create condition variable
pthread_cond_t cond;

struct Node{
    int num;
    struct Node *next;
};

// Head node
struct Node * head = NULL;

void * producer(void * arg) {

    // Constantly create new nodes and add them to the linked list
    while(1) {
        pthread_mutex_lock(&mutex);
        struct Node * newNode = (struct Node *)malloc(sizeof(struct Node));
        newNode->next = head;
        head = newNode;
        newNode->num = rand() % 1000;
        printf("add node, num : %d, tid : %ld\n", newNode->num, pthread_self());
        
        // As long as one is produced, consumers will be notified of consumption
        pthread_cond_signal(&cond);

        pthread_mutex_unlock(&mutex);
        usleep(100);
    }

    return NULL;
}

void * customer(void * arg) {

    while(1) {
        pthread_mutex_lock(&mutex);
        // Save pointer to header node
        struct Node * tmp = head;
        // Determine whether there is data
        if(head != NULL) {
            // Have data
            head = head->next;
            printf("del node, num : %d, tid : %ld\n", tmp->num, pthread_self());
            free(tmp);
            pthread_mutex_unlock(&mutex);
            usleep(100);
        } else {
            // No data, need to wait
            // When this function call is blocked, the mutex lock will be unlocked. When it is not blocked, continue to execute downward and re lock it.
            pthread_cond_wait(&cond, &mutex);
            pthread_mutex_unlock(&mutex);
        }
    }
    return  NULL;
}

int main() {

    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&cond, NULL);

    // Create 5 producer threads and 5 consumer threads
    pthread_t ptids[5], ctids[5];

    for(int i = 0; i < 5; i++) {
        pthread_create(&ptids[i], NULL, producer, NULL);
        pthread_create(&ctids[i], NULL, customer, NULL);
    }

    for(int i = 0; i < 5; i++) {
        pthread_detach(ptids[i]);
        pthread_detach(ctids[i]);
    }

    while(1) {
        sleep(10);
    }

    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);

    pthread_exit(NULL);

    return 0;
}

Semaphore

  • Semaphore type sem_t
  • int sem_init(sem_t *sem, int pshared, unsigned int value);
  • int sem_destroy(sem_t *sem);
  • int sem_wait(sem_t *sem);
  • int sem_trywait(sem_t *sem);
  • int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
  • int sem_post(sem_t *sem);
  • int sem_getvalue(sem_t *sem, int *sval);

semphore.c

/*
    Semaphore type sem_t
    int sem_init(sem_t *sem, int pshared, unsigned int value);
        - Initialization semaphore
        - Parameters:
            - sem : Address of semaphore variable
            - pshared : 0 Used between processes, non-0 used between processes
            - value : Value in semaphore

    int sem_destroy(sem_t *sem);
        - Release resources

    int sem_wait(sem_t *sem);
        - Lock the semaphore and call the semaphore value of - 1 once. If the value is 0, it will be blocked

    int sem_trywait(sem_t *sem);

    int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
    int sem_post(sem_t *sem);
        - Unlock the semaphore and call the value of semaphore + 1 once

    int sem_getvalue(sem_t *sem, int *sval);

    sem_t psem;
    sem_t csem;
    init(psem, 0, 8);
    init(csem, 0, 0);

    producer() {
        sem_wait(&psem);
        sem_post(&csem)
    }

    customer() {
        sem_wait(&csem);
        sem_post(&psem)
    }

*/

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

// Create a mutex
pthread_mutex_t mutex;
// Create two semaphores
sem_t psem;
sem_t csem;

struct Node{
    int num;
    struct Node *next;
};

// Head node
struct Node * head = NULL;

void * producer(void * arg) {

    // Constantly create new nodes and add them to the linked list
    while(1) {
        sem_wait(&psem);
        pthread_mutex_lock(&mutex);
        struct Node * newNode = (struct Node *)malloc(sizeof(struct Node));
        newNode->next = head;
        head = newNode;
        newNode->num = rand() % 1000;
        printf("add node, num : %d, tid : %ld\n", newNode->num, pthread_self());
        pthread_mutex_unlock(&mutex);
        sem_post(&csem);
    }

    return NULL;
}

void * customer(void * arg) {

    while(1) {
        sem_wait(&csem);
        pthread_mutex_lock(&mutex);
        // Save pointer to header node
        struct Node * tmp = head;
        head = head->next;
        printf("del node, num : %d, tid : %ld\n", tmp->num, pthread_self());
        free(tmp);
        pthread_mutex_unlock(&mutex);
        sem_post(&psem);
       
    }
    return  NULL;
}

int main() {

    pthread_mutex_init(&mutex, NULL);
    sem_init(&psem, 0, 8);
    sem_init(&csem, 0, 0);

    // Create 5 producer threads and 5 consumer threads
    pthread_t ptids[5], ctids[5];

    for(int i = 0; i < 5; i++) {
        pthread_create(&ptids[i], NULL, producer, NULL);
        pthread_create(&ctids[i], NULL, customer, NULL);
    }

    for(int i = 0; i < 5; i++) {
        pthread_detach(ptids[i]);
        pthread_detach(ctids[i]);
    }

    while(1) {
        sleep(10);
    }

    pthread_mutex_destroy(&mutex);

    pthread_exit(NULL);

    return 0;
}

Keywords: Linux

Added by chrisio on Wed, 22 Sep 2021 02:53:14 +0300