Linux multi-tasking programming (2)

Foreword:

What thread:

In order to further reduce the idle time of the processor, support multi-processors and reduce the overhead of context switching, another concept has emerged during the evolution of the process: threads. It is an independent running route within the process and is the smallest unit of kernel scheduling. It is also called a lightweight process. It is widely used in embedded development.

simply say:

Threads are lightweight processes and do not occupy as much space as processes. Threads are directly connected to each other, and the space is not so independent.

effect:

        When making certain small functions, because the code area is relatively small and the space assignment is unclear, using processes is less cost-effective and prone to space errors. In this concept, people have introduced the concept of threads, further subdividing tasks on the basis of processes, reducing their size, making them take up less space, and the spaces between them are relatively less independent, realizing resource sharing operations.

Thread introduction:

1. Concept:

线程是函数的一次执行过程,该函数 被称为:线程函数 格式如下:
    void *线程函数名(void *arg)
              {
                  ...
                  ...
                  ...
              }

2. Function:

在同一个进程中创建的线程共享该进程的地址空间 

3>同一进程同 线程与线程之间的共享资源和私有资源
共享资源:                                 私有资源:
可执行的指令                                    每个线程私有的资源如下
静态数据                                        线程ID (TID)
进程中打开的文件描述符                           PC(程序计数器)和相关寄存器
信号处理函数                                    堆栈
当前工作目录                                    局部变量
用户ID                                         返回地址
用户组ID                                       错误号 (errno)
                                               信号掩码和优先级      
                                                执行状态和属性
               
       

3、API:

        Create thread:

pthread_create

#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
Compile and link with -pthread.
 功能: 
 创建线程 
                        
     参数: 
         1>thread:保存线程的变量地址/线程的标志 
         2>attr:线程的属性:两种属性:非分离-->默认 -->NULL 
                                   分离 -->需要手动设置
         3>start_routine:线程函数的地址 -->入口 ->回调函数
         4>arg:给线程函数传入的参数
                        
     返回值: 
            成功返回0 
            失败返回-1,并设置错误码
                        
PS:打印线程ID号 
   pthread_self 
   #include <pthread.h>
   pthread_t pthread_self(void);
    功能:
         打印执行该函数的线程ID 
    参数:
        无 
                        
    返回值: 
         成功返回线程ID


注意:
    linux的线程函数库,并不是自带的,而是来自第三方库-->工具库


如果我们编译带第三方库的函数时,需要加上链接该库的操作 
-l库名  :其他库:网络编程:JSON  SQLITE3: -ljson  -lsqlite3
我们现在用线程库:编译的末尾加上:-lpthread




        Thread death:

pthread_exit
#include <pthread.h>
void pthread_exit(void *retval);
Compile and link with -pthread.
功能: 
     线程的死亡 :该函数放在线程:结束线程    放在进程中:结束进程
                    
参数: 
     retval:万能指针型   遗言

        Thread collection (manual)

pthread_join
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
Compile and link with -pthread.
功能: 
     阻塞等待收尸:只能收非分离属性的线性尸体
                       
参数: 
      thread:要收尸的线程地址
      retval:二级指针遗言,从exit中提取 
                        
返回值: 
        成功返回0 
        失败返回-1,并设置错误码

        Automatically collect corpses (set detachment attributes): pthread_detach (thread id number);

        Set properties:

设置分离属性:

1>初始化保存属性的变量地址 
pthread_attr_init
#include <pthread.h>
int pthread_attr_init(pthread_attr_t *attr);
功能: 
     初始化保存属性的变量地址 
参数: 
     attr:属性的变量地址 
                            
返回值: 
      成功返回0 
      失败返回非0的错误码
                    
2>设置属性 
pthread_attr_setdetachstate
#include <pthread.h>
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
功能: 
     设置线程属性 
                           
      参数: 
           1>attr:保存属性的变量地址 
           2>detachstate:属性状态
           PTHREAD_CREATE_DETACHED
                           ----->分离属性 

          PTHREAD_CREATE_JOINABLE
                     ----->非分离属性
       返回值: 
             成功返回0 
             失败返回非0的错误码
3>创建线程
pthread_create

4>销毁属性
pthread_attr_destory
#include <pthread.h>
int pthread_attr_destroy(pthread_attr_t *attr);
功能: 
    消除attr中保存的属性 
                            
参数: 
     1>attr:保存属性的变量地址 
返回值: 
      成功返回0 
      失败返回非0的错误码




Set properties: Broken version:

>创建非分离属性: 
pthread_create(,NULL,,);
                           
                           
2>将线程的属性设置为分离属性 
pthread_detach
#include <pthread.h>
int pthread_detach(pthread_t thread);
Compile and link with -pthread.
      功能: 
          设置线程为分离属性 
                                
      参数: 
          thread:线程的标志 
                                
        返回值: 
               成功返回0 
               失败返回非0的错误码

4. Thread synchronization and mutual exclusion:

        Signal amount:

Named semaphore:

       POSIX named semaphore: can be used between processes (a system-level resource, which can basically replace the semaphore in IPC)

        The name of this kind of named semaphore consists of a string like "/somename". Note that there is a forward slash in front of it. Such a
semaphore is actually a special file. After it is successfully created, it will be placed in a special file on the system. In the virtual file system
/dev/shm, as long as different processes agree on the same name, they can coordinate with each other through this named semaphore.


It is worth mentioning that named semaphores and system-V semaphores are system-wide. They will not disappear automatically      after the process exits , but need to be manually deleted and resources released.

POSIX 有名信号量的一般使用步骤是:
1,使用 sem_open( )来创建或者打开一个有名信号量。
2,使用 sem_wait( )和 sem_post( )来分别进行 P 操作和 V 操作。
3,使用 sem_close( )来关闭他。
4,使用 sem_unlink( )来删除他,并释放系统资源。





API:

        Create a named semaphore:

        PV operation:

Unlike system-V semaphores that can apply for or release more than 1 resource, for POSIX semaphores, the number of resources applied for and released each time is 1. Calling sem_wait() will cause blocking when the resource is 0. If you do not want to block and wait, you can use sem_trywait() instead.

        Close or delete a semaphore:


Unnamed semaphore:

        POSIX unnamed semaphore: can only be used within the process and between threads (using the same memory area).

           If we want to solve the synchronization mutual exclusion between threads within a process, then maybe there is no need to use a named semaphore
, because these threads share the same memory space, we can define a more lightweight, memory-based (not in
any Internal file system) unnamed semaphore to achieve the purpose.     

这种信号量的使用步骤是:
1,在这些线程都能访问到的区域定义这种变量(比如全局变量),类型是 sem_t。
2,在任何线程使用它之前,用 sem_init( )初始化他。
3,使用 sem_wait( )/sem_trywait( )和 sem_post( )来分别进行 P、V 操作。
4,不再需要时,使用 sem_destroy( )来销毁他。

        Initialize the unnamed semaphore:

        

ps: Unnamed semaphores are generally used between threads within a process, so the pshared parameter is generally 0. When using this kind of semaphore
between processes, it must be defined in a place that each process can access - such as shared memory.


Three types of semaphores: system-V semaphores and POSIX semaphores (named-sem and unnamed-sem). The differences:

1,sys-V 信号量较古老,语法艰涩。POSIX 信号量简单,轻量。
2,sys-V 信号量可以对代表多种资源的多个信号量元素同一时间进行原子性的 P/V
操作,POSIX 信号量每次只能操作一个信号量。
3,sys-V 信号量和 named-sem 是系统范围的资源,进程消失之后继续存在,而
unnamed-sem 是进程范围的资源,随着进程的退出而消失。
4,sys-V 信号量的 P/V 操作可以对信号量元素加减大于 1 的数值,而 POSIX 信号量
每次 P/V 操作都是加减 1。
5,sys-V 信号量甚至还支持撤销操作——一个进程对 sys-V 信号量进行 P/V 操作时可以给 该
操作贴上需要撤销的标识,那么当进程退出之后,系统会自动撤销那些做了标识的操作。而 POSIX 信
号没有此功能。
6,sys-V 信号量和 named-sem 适合用在进程间同步互斥,而 unamed-sem 适合用在线程
间同步互斥。

        Thread mutual exclusion: Thread mutual exclusion lock:

       Reason: Introduction of mutex lock: mainly used to ensure the integrity of shared data and to protect critical resources.

Before consulting the man manual

sudo apt-get install manpages-posix-dev

step:

操作步骤:
1. 初始化互斥锁资源 pthread_mutex_init()
2. 访问某个资源之前先上锁 pthread_mutex_lock()
3. 访问结束后应该解锁 pthread_mutex_unlock()
4. 当不再使用的时候应该销毁到锁资源 pthread_mutex_destroy ( )

Initialization lock: pthread_mutex_init();

#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
功能: 
      初始化互斥锁 
                            
参数: 
     1>mutex:锁的标志 /锁的变量地址 
     2>attr:锁的属性 -->NULL互斥 
                            
返回值: 
       成功返回0 
       失败返回错误码

Use lock: lock: pthread_mutex_lock

#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
功能:     
     上锁 
参数: 
    mutex:锁的标志
                            
返回值:
      成功返回0 
      失败返回错误码  

       Unlock: pthread_mutex_unlock()

#include <pthread.h>
int pthread_mutex_unlock(pthread_mutex_t *mutex);
功能:     
     解锁 
参数: 
      mutex:锁的标志
                            
返回值:
      成功返回0 
      失败返回错误码 

           Destroy the lock:

pthread_mutex_destory
#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);
功能:     
    销毁锁 
参数: 
    mutex:锁的标志
                            
返回值:
       成功返回0 
       失败返回错误码 

        When read-write locks
        use mutex locks, they can effectively protect a shared resource so that only one thread can access the resource under any circumstances. If multiple threads are reading a certain resource, multiple threads will be blocked and waiting to obtain the resource, resulting in reduced program efficiency.
        Therefore, in the above situation, if a resource is likely to be accessed by multiple threads at the same time, then a read-write lock can be used.


Read lock: multiple threads are allowed to read resources at the same time, and multiple read locks can be added at the same time


Write lock: Only one thread is allowed to read the resource at the same time, and no other thread is allowed to hold the lock resource.

        step:

操作步骤:
1. 初始化读写锁pthread_rwlock_init()
2. 添加读锁 / 写锁pthread_rwlock_rdlock / pthread_rwlock_wrlock
3. 当不再使用共享资源的时候解锁pthread_rwlock_unlock()
4. 当不需要使用读写锁时可以销毁pthread_rwlock_destroy()

Initialize/destroy read-write lock:

        

头文件:
#include <pthread.h>
函数原型:
//销毁锁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
//初始化锁
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
const pthread_rwlockattr_t *restrict attr);
参数分析:
         rwlock --> 需要初始化/销毁的读写锁资源
         attr --> 初始化所资源的属性 NULL 默认属性
返回值:
        成功 返回0
        失败 返回错误号码

Blocking add read/write lock/non-blocking add read/write lock/unlock:

头文件:
 #include <pthread.h>
 函数原型:
 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 --> 需要操作的读写锁资源
 返回值:
         成功 返回0
         失败 返回错误号码

ps:
Whether it is a read lock or a write lock, you need to determine the status of the current shared resource when locking:
        Read lock: When a thread needs to read a message, it should first check whether there is a write lock, and if so, Wait, otherwise add a read lock.
        Write lock: When a thread needs to write a message, it should first check whether someone is reading, and if so, wait. Otherwise, add a write lock and prevent other threads from entering.


Condition variable:

        Condition variables are another synchronization mutex mechanism, and they must be used together with mutex locks.

effect:

When multiple threads need to obtain the same resource for operation, if the current resource amount is not empty, then all threads to
obtain the resource need to enter a waiting queue of condition variables to wait for the resource data to arrive. When the resource When the data arrives, the condition variable can choose to wake up the thread from the queue to read the resource (the wake-up can wake up one or more).

Initialization conditions/destruction variables:

Waiting for resources:

        

Wake up condition variable: waiting thread:

The above two functions are used to wake up threads blocked in the condition variable waiting queue. As the name implies: broadcast is used to wake up all threads, and signal is used to wake up a waiting thread.

Note: The awakened thread cannot return immediately from pthread_cond-wait(), but must obtain the matching mutex lock.

Guess you like

Origin blog.csdn.net/apple_71040140/article/details/132569266