线程与多线程(同步、互斥)

本文详细介绍了Linux下线程的概念、线程与进程的区别、线程的优缺点,以及线程的创建、终止、等待和分离。重点讨论了线程安全问题,包括同步、互斥和三种线程同步机制:POSIX信号量、互斥量和条件变量。通过示例阐述了如何避免死锁,并介绍了可重入函数在多线程环境中的重要性。
摘要由CSDN通过智能技术生成

      什么是线程:线程是进程中的一条执行流,linux下线程是以进程的PCB模拟的,linux下的线程就是轻量级进程,因此linux下的线程是CPU调度的基本单位(线程是实际上处理事件的)。linux下的进程是线程组,进程id==线程组id,所以进程是资源分配的基本单位,线程是进程的一个执行单位,一个进程可以有一个或多个线程,它们共用该进程的所有资源。线程没有独立的内存空间。 linux下的线程共享虚拟地址空间(代码段和数据段)。

     线程是程序中完成一个独立任务的、一个可调度的实体。根据运行环境和调度者可以分为内核线程和用户线程,内核线程运行在内核空间,由内核调度;用户线程运行在用户空间,由线程库来调度。

    进程之间相互独立,是系统分配资源的最小单位;同一个进程中的线程共享资源。

总结下来就是:

(1)线程是进程的一部分

(2)CPU调度的是线程

(3)系统为进程分配资源,不对线程分配资源

线程和进程线程是cpu的调度基本单位,进程是资源分配基本单位。

            线程优点:线程的创建/销毁成本更低----线程的调度切换成本也会更低。

                              共享虚拟地址空间,所以线程间的通信更加方便,线程的执行力度更加细致。

                              能充分利用多处理器提高可并行数量

            线程缺点:缺乏访问控制---线程安全需要考虑更多,有些系统调用和程序异常是针对整个进程产生影响。

                      多个线程对临界资源(公共资源)进行操作会造成数据混乱,健壮性降低。

而进程:因为独立性,所以健壮性比较强,但是通信麻烦,资源成本高 。 

线程共享:  文件状态表      用户id组id       虚拟地址空间       信号处理方式      

线程独有:(相对独有---因为独有的数据还是在虚拟地址空间中)  栈区、上下文数据、线程id、errno、信号屏蔽字。 

 

 线程控制

      1.创建线程

                     

                                

         创建一个线程后,Do具体去做其对应的事情,tid的地址中存放线程的ID,ret用来接收返回值。

2.线程终止

       想要终止一个线程有以下几种方式:

  • 在线程中用return返回,就结束了该线程,在主进程中return返回后,是退出了整个进程。
  • 在线程中调用pthread_exit( ),可以结束当前线程。
  • 一个线程可以调用pthread_cancel来结束同一个进程中的线程。


    3.线程等待         

      线程等待的作用是已经退出的线程,其空间没有被释放,仍然在进程的地址空间内,创建新的线程不会复用之前的线程地址。所以线程退出后,需要对其进行pthread_join( )等待,否则无法释放资源,造成资源泄露。

                                           

    4.线程分离

       如果不关心线程的返回值,就不需要使用pthread_join了,可以使用pthread_detach,当线程退出后,系统自动释放其资源。在一个线程中使用方法:

 

 线程安全

      当多个线程访问共享资源时,如果不考虑线程的访问方式和执行顺序,那么该资源是不安全的。由于线程共享进程虚拟地址空间,使得线程之间的通信变得十分方便,但是有可能多个线程会对同一数据进行操作,而该数据的安全访问就变得十分重要。因为多个线程对临界资源的操作会导致逻辑的混乱/数据二义性,因此就有了线程安全。保证数据的安全访问机制----同步/互斥

三种用于线程同步的机制:(有demo)

     1.POSIX信号量:POSIX的无名信号量用于解决线程间的同步问题。以下是POSIX信号量的相关操作:

sem_init:用来创建一个POSIX信号量
int sem_init(sem_t *sem, int pshared, unsigned int value);
    创建一个匿名的信号量,pshared值为0时,代表当前信号量是进程内的局部信号量(线程之间共享),否则就代表进程之间(多个进程)共享。
    value参数用于指定信号量的初始值。

sem_destroy:用来销毁一个信号量,释放其占用的内核资源
int sem_destroy(sem_t *sem);
    切忌:不可释放一个正在被其他线程等待的信号量

sem_wait:以原子操作来对信号量减一
int sem_wait(sem_t *sem);
    若信号量的值小于等于0,表示没有资源可以用,此时sem_wait被阻塞,直到信号量大于零

sem_post:以原子操作对信号量加一
int sem_post(sem_t *sem);
    当信号量的值大于1时,阻塞等待的线程被唤醒

以上函数成功返回0,失败返回-1
    

     与进程间通信中的System V版本的信号量不太一样,posix信号量指的是单个计数信号量,而IPC中的System V,主要是计数信号量集 

    2.互斥量(互斥锁):用于保护关键代码段,以确保其独有的访问权限。当进入关键代码段时,我们要对其加锁;离开时,需要解锁,以唤醒其他等待该互斥锁的线程。例如,这是一个最开始的售票系统,当在没有加锁的时候四个线程可以并发的对ticket进行操作,出现问题,因为多线程可以并发的访问ticket,所以会出现ticket变为负数。解决方法就是加上锁,这样就相当于四个线程对于ticket的访问是同步(按访问顺序进行操作)的。

创建锁:int pthread_mutex_init(pthread_mutex_t *restrict mutex,
              const pthread_mutexattr_t *restrict attr);
        //一般将第二个参数设为NULL

销毁锁:int pthread_mutex_destroy(pthread_mutex_t *mutex);

加锁:int pthread_mutex_lock(pthread_mutex_t *mutex);

解锁:int pthread_mutex_unlock(pthread_mutex_t *mutex);

其中第一个参数都是pthread_mutex_t类型的。

    3.条件变量:互斥锁是用来对共享数据访问的同步,那么条件变量就是用于在线程之间同步共享数据的值。条件变量提供了一种线程之间的通知机制,当某个共享数据满足一定条件时,唤醒正在等待这个共享数据的线程。着重了解一下条件变量的应用:消费者--生产者模型生产或消费完都需要唤醒正在等待的一方做事情了

初始化条件变量:pthread_cond_init
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
    attr:用来设置条件变量的属性,设置为NULL,是默认属性

销毁条件变量:pthread_cond_destroy
int pthread_cond_destroy(pthread_cond_t *cond); 
    销毁条件变量,释放占用的内核资源
    注意销毁一个正在被等待的条件变量是不被允许的,失败并返回EBUSY

唤醒等待的线程:pthread_cond_broadcast
int pthread_cond_broadcast(pthread_cond_t *cond);
    以广播的方式唤醒所有等待目标条件变量的线程
int pthread_cond_signal(pthread_cond_t *cond);
    只唤醒一个等待目标条件变量的线程,取决于优先级和调度策略

等待目标条件变量:pthread_cond_wait
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
    mutex:是用于保护条件变量的互斥锁,以确保该操作的原子性。
    
    上面的函数第一个参数都指的是要操作的条件变量,类型是pthread_cond_t的结构体
    成功返回0,失败返回错误码
    

 

    一:线程互斥:保证数据同一时间的唯一访问性。用到了互斥锁:某个线程想要访问某个共享资源时,对其加锁,此时只有该线程可以对其访问,其他线程不能更改锁的状态;当操作完成后,解锁供其他线程使用。注意成对出现,别造成死锁。

死锁的四个必要条件

  • 互斥条件:一个资源每次只能被一个执行流执行
  • 不可剥夺条件:对于已获取的资源,未使用完之前,别的执行流不能强行获取
  • 请求与保持:拿到一个资源,保持着不释放,还去请求别的资源
  • 环路等待:若干条执行流形成首尾成环的循环等待资源的关系,后面的一直等前面的。

解决死锁(预防)注意

  • 打破死锁的四个必要条件
  • 注意加锁、解锁的顺序一致
  • 别忘记释放锁
  • 资源一次性分配:一次性分配所有资源,这样就不会再请求了(打破请求条件)

 

   二: 线程同步:保证访问数据的时序性。用到了条件变量:

 

可重入函数:如果一个函数能被多个线程同时调用而不发生竞态条件,是线程安全的,则称该函数为可重入函数。

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值