Linux多任务编程(二)

前言:

什么线程:

为了进一步减少处理器的空转时间,支持多处理器以及减少上下文切换的开销,进程在演化过程中出现了另外一种概念:线程。他是进程内独立的一条运行路线,是内核调度的最小单元,也被称为轻量化的进程。在嵌入式开发中广泛运用。

简单说:

线程是轻量化级别进程  空间占用没有进程大,线程与线程直接,空间也没有这么独立。

作用:

        在制作某些小功能时,因为代码区相对较小,且空间交代不明确的情况下使用进程,性价比较低,且容易出现空间错误。在此概念,人们引入了线程的概念,在进程的基础上进一步细分任务,将其体型削减,让它占用空间更小,彼此之间空间相对没这么独立,实现资源共享的操作 。

线程介绍:

1.概念:

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

2、作用:

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

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

3、API:

        创建线程:

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




        线程的死亡:

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

        线程的收尸(手动)

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

        自动收尸(设置分离属性):pthread_detach(线程id号);

        设置属性:

设置分离属性:

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的错误码




设置属性:摆烂版:

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

4、线程的同步与互斥:

        信号量:

有名信号量:

       POSIX 有名信号量:可以在进程之间使用(属于系统级别的资源,基本可以替代IPC中的信号量)

        这种有名信号量的名字由类似“/somename”这样的字符串组成,注意前面有一个正斜杠,这样
的信号量其实是一个特殊的文件,创建成功之后将会被放置在系统的一个特殊的虚拟文件系
统/dev/shm 之中,不同的进程间只要约定好一个相同的名字,他们就可以通过这种有名信号量来相互协调。

     值得一提的是,有名信号量跟 system-V 的信号量都是系统范畴的,在进程退出之后
他们并不会自动消失,而需要手工删除并释放资源。

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





API:

        创建一个有名信号量:

        PV操作:

不像 system-V 的信号量可以申请或者释放超过 1 个资源,对于 POSIX 信号量而言,每次申请和释放的资源数都是 1。其中调用 sem_wait( )在资源为 0 时会导致阻塞,如果不想阻塞等待,可以使用 sem_trywait( )来替代。

        关闭或删除信号量:


无名信号量:

        POSIX 无名信号量:只能在进程的内部,线程之间互相使用(使用同一个内存区)。

           如果我们要解决的是一个进程内部的线程间的同步互斥,那么也许不需要使用有名信号
量,因为这些线程共享同一个内存空间,我们可以定义更加轻量化的、基于内存的(不在任
何文件系统内部)无名信号量来达到目的。     

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

        初始化无名信号量:

        

ps:无名信号量一般用在进程内的线程间,因此 pshared 参数一般都为 0。当将此种信号
量用在进程间时,必须将他定义在各个进程都能访问的地方——比如共享内存之中。


三种信号量:system-V 信号量和 POSIX 信号量(named-sem 和unnamed-sem),区别:

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 适合用在线程
间同步互斥。

        线程的互斥:线程互斥锁:

       原因:  引入互斥锁:主要是用来保证共享数据的完整性 用来保护临界资源。

查询man手册之前

sudo apt-get install manpages-posix-dev

步骤:

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

初始化锁: 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 
       失败返回错误码

使用锁:上锁:pthread_mutex_lock

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

       解开锁:pthread_mutex_unlock()

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

           销毁锁:

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

        读写锁
        使用互斥锁的时候, 可以有效的保护某一个共共享资源, 使得该资源在任何情况下都只能有一个线程在访问。如果出现多个线程进行读取某一资源的时候就会造成多个线程在获取该资源是阻塞等待, 导致程序的效率降低。
        因此以以上情况中,如果某一个资源有可能在同一时间内会有多个线程进行同时访问,那么就可以使用读写锁。


读锁:在同一时间内允许有多个线程进行读取资源,可以同时添加多个读锁


写锁:在同一个时间内只允许有一个线程进行读取资源, 不允许有其它线程持有锁资源

        步骤:

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

初始化/销毁读写锁:

        

头文件:
#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
        失败 返回错误号码

阻塞添加读/写锁/非阻塞添加读/写锁/解锁:

头文件:
 #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:
不管是读锁还是写锁,在加锁的时候都需要判断当前共享资源的状态:
        读锁: 当一个线程需要读取消息的时候,应该先检查是否有写锁的存在,如果有则等待,否则添加读锁。
        写锁: 当一个线程需要写入某个消息的时候,应该先检查是否有人正在读取,如果有则等待,否则添加写锁,进制其它线程进入。


条件变量:

        条件变量是另外一种同步互斥机制,他必须和互斥锁一同配合使用。

作用:

当有多个线程需要获得同一个资源进行操作的时候,如果当前资源量未空, 那么就需要让所有来
获取资源的线程进入一个条件变量的等待队列中进行等待,等待资源数据到达, 当资源数据到达的时候条件变量则可以选择从队列中唤醒线程进行读取资源(唤醒可以唤醒一个或多个)。

初始化条件/销毁变量:

等待资源:

        

唤醒条件变量:等待线程:

以上两个函数用来唤醒阻塞在条件变量等待队列的线程,顾名思义:broadcast是用来唤醒全部线程,signal是用来唤醒一个等待的线程。

注意:被唤醒的线程并不能立即从pthread_cond-wait()中返回,而是必须获取配套的互斥锁。

猜你喜欢

转载自blog.csdn.net/apple_71040140/article/details/132569266