走进线程-多线程

线程终止

Linux下有2种方式终止线程:

  • 通过return从线程函数返回
  • 通过调用函数pthread_exit()是线程退出

    #include <pthread.h>
    void pthread_exit(void *retval);

! ——note!

  1. 在主线程中如果从main函数返回或者调用了exit函数退出主线程,则整个进程到此终止,此时整个进程中的线程也都终止,所以主线程中不能过早的从main函数中返回。
  2. 如果主线程调用pthread_exit函数,则仅仅是主线程到此终结,进程不会结束,进程内的其他线程也不会结束,直到所有线程结束进程才会结束。

临界资源在一段时间内只能被一个线程占有,其他线程想要使用临界资源要先提出申请,如果该资源被占用就要等待,但如果占用临界资源的线程结束时没有进行释放临界资源

线程同步

Linux提供了多种处理线程间同步问题的方式,最常用的有互斥锁、条件变量、异步信号

互斥锁

通过锁机制来实现线程间的同步,在同一时刻通常它只允许一个线程执行一个关键部分的代码 。

  • 使用互斥锁之前必须进行初始化操作,有两种方式:
  1. 静态赋值,将宏结构常量PTHREAD_MUTEX_INITIALIZER赋给互斥锁:
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
  2. 通过pthread_mutex_init函数进行初始化互斥锁:
    int pthread_mutex_init (pthread_mutex_t *mutex,const pthread_mutexattr_t mutexattr);
    其中参数mutexattr表示互斥锁的属性,如果是NULL则为默认属性。
    互斥锁的属性
    1. 普通锁:当一个线程加锁后,其余请求加锁的线程形成等候队列,解锁之后按照优先级获得锁
    2. 嵌套锁:允许一个线程对同一个锁多次加锁,并通过多次uclock解锁,如果不是同线程请求,则在解锁时重新竞争。
    3. 检错锁:在同一进程请求同一个锁的情况下返回EDEADLK,否则执行的动作与类型PTHREAD_MUTEX_NP相同。
    4. 适应锁:解锁后重新竞争
  • 初始化之后就能给互斥锁加锁了,有两个函数:

① int pthread_mutex_lock(pthread_mutex_t *mutex);
② int pthread_mutex_trylock(pthread_mutex *mutex);

用①**pthread_mutex_lock()**时,如果mutex已经被锁住,那么当前线程就会阻塞,直到互斥锁被其他线程释放。所以,当该函数返回时,说明互斥锁已经被当前线程成功加锁。

用②**pthread_mutex_trylock()**时,如果mutex已经被加锁则直接返回,返回的错误码是EBUSY,并不等待。

所有的锁都不会被两个不同的线程同时得到,其中一个必须等待解锁,在统一进程中的线程如果加锁之后没有解锁,那么其他线程将无法再获得该锁

  • 用pthread_mutex_unlock函数解锁
    这个时候互斥锁一定要是加锁状态,调用解锁函数的线程一定要是加锁的线程。
    解锁之后如果有其他线程在等待互斥锁,等待队列中的第一个将获得互斥锁。

  • 当一个互斥锁使用完之后,要进行清除,使用函数pthread_mutex_destroy

int pthread_mutex_destroy (pthread_mutex_t *mutex);

清楚一个互斥锁意味着释放他所占用的资源,在清除时要保证它是出于开放状态,若处于锁定状态则返回EBUSY,成功返回0.
在Linux中互斥锁并不占用内存,所以pthread_mutex_destroy()函数除了解除互斥锁的状态意外没有其他操作

就是说,有没有锁是一个概念,有锁之后,用没用又是一个概念。用函数pthread_mutex_init()一个互斥锁就相当于拥有了一把锁,调用pthread_mutex_lock()就相当于锁上了这把锁,调用pthread_mutex_unlock()就是打开了这把锁,函数pthread_mutex_destroy()就是扔掉了这把锁,拥有一把锁就意味着占用了一部分资源独享

条件变量

一个线程X在等待某个条件成立,该条件成立的时候等待的这个X线程才继续向下执行;另一个线程Y可以发出唤醒信号来唤醒X线程等待的条件,条件就是pthread_cond_t ->>条件变量。 (条件变量的作用在线程同步中也可以通过信号量来实现,但信号量的性能不如pthread库中的条件变量)

  • int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
  • int pthread_cond_broadcast(pthread_cond_t *cond);
  • int pthread_cond_signal(pthread_cond_t *cond);

同样,使用条件变量之前需要初始化,两种初始化的方法: (条件变量应该声明为全局可见的,会在多个线程中被访问)

  1. 通过预定义的初始化宏静态初始化
    pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
  2. 通过函数动态初始化
    pthread_cond_t cond;
    pthread_cond_init(&cond, NULL);

并且在不再使用条件变量的时候将初始化的条件变量进行销毁:

pthread_cond_destroy(&cond);

在条件变量从初始化到被销毁的正常生命期间,就可以对它调用最上面列出的三个API。
大体来说,主要的行为是:
1. 解开mutex锁;
2. 改变线程等待的条件变量值为真
3. 条件变量为真时加锁

但是pthread_cond_signal()函数在唤醒线程时不能够像信号量中的sem_post函数一样做到绝对的只唤醒一个线程,但是只能允许一个信号访问也就是说只有一个线程被允许执行下面接下来的代码,所以要在等待的线程被唤醒处用while循环判断是否符合条件变量为真的条件,以保证没有线程的虚假唤醒,而不是一次性条件判断if,这样就可能导致有多个线程同时唤醒一起执行接下来的代码导致程序出现问题。

pthread_mutex_lock(&mutex);
while (条件为假)
    pthread_cond_wait(cond, mutex);
	(修改条件)
pthread_mutex_unlock(&mutex);

在这里学到了更多的针对虚假唤醒的处理方法以及理解,->> 条件变量正确使用方法以及理解!

同时,生产者消费者模型也是通过条件变量来实现的。

pthread_join() & pthread_detach()

join是三种同步线程的方法之一,调用pthread_join()将阻塞自己,一直要等待直到加入的线程运行结束,可以通过pthread_join()获取线程的返回值,并且一个线程对应一个pthread_join()函数,对同一个线程进行多次pthread_join()调用是逻辑错误

在这里也可以将线程分两类:一种可以join(joinable),另一种不可以,该属性在创建线程的时候指定。
joinable线程在创建后,调用pthread_detach()显式的分离,并且分离后不可以再次合并也就是说,这一步的操作不可逆。并且不是所有的POSIX实现都将joinable设置为默认,所以在创建线程的时候就应该明确该线程的join和detach属性。
调用pthread_join的线程会阻塞,知道指定的线程返回,调用了pthread_exit(),或者被取消。
detach线程如果在线程结束运行并且没有进行pthread_join(),那么它就类似于僵尸进程的存在,还留有一部分的不可释放资源(退出状态码)在内存中,所以创建线程应该调用pthread_join()来等待线程结束并得到线程退出代码,收回资源(wait/waitpid类似),但是如果创建线程在调用pthread_join后,被等待的线程还没有结束运行,那么主线程就会一直处于阻塞状态,但这种情况不是预期的状态,所以要实现更好的资源收回并且不使线程无效等待就可以在子线程中加入pthread_detach(pthread_self()),或者父线程调用pthread_detach(thread_id)) 如果非阻塞就立即返回也就是说将该子进程设置状态为detached,子线程运行结束后会自动释放自己的所有资源。

pthread_join()的调用者将挂起并等待th线程终止,retval是pthread_exit()调用者线程(线程ID为th)的返回值,如 果thread_return不为NULL,则thread_return=retval。需要注意的是一个线程仅允许唯一的一个线程使用 pthread_join()等待它的终止,并且被等待的线程应该处于可join状态,即非DETACHED状态 ;
如果进程中的某个线程执行了pthread_detach(th),则th线程将处于DETACHED状态,这使得th线程在结束运行时自行释放所占用的 内存资源,同时也无法由pthread_join()同步,pthread_detach()执行之后,对th请求pthread_join()将返回错误 ;
一个可join的线程所占用的内存仅当有线程对其执行了pthread_join()后才会释放,因此为了避免内存泄漏,所有线程的终止,要么已设为DETACHED,要么就需要使用pthread_join()来回收 *

线程的退出
用pthread_exit只会使主线程自身退出,产生的子线程继续执行;用return则所有线程退出。
想要子线程能完整退出,①主线程中调用pthread_join()等待;② 主线程退出时pthread_exit(),子线程还能够继续执行;③ 子线程创建时调用pthread_detach(),这个时候就要主线程在子线程完成之前不退出。

读写锁

在对临界资源的访问中,更多的是读操作,相对来说写操作较少,但互斥锁是不会区分对临界资源的读写操作的,所以只有互斥锁可能会影响访问效率。

一般情况下,应该允许多个线程对临界变量进行读操作,只需要控制唯一线程能够进行写操作就好,这就要求对临界资源的访问控制粒度更加精细。
这个时候就不是两个线程间的互斥关系,而是读操作-写操作 & 写操作-写操作互斥关系了。
缓冲区不满,允许进行写操作;缓冲区不空,允许进行读操作

  • 读写锁的操作

  1. 定义读写锁变量
  2. 初始化读写锁 pthread_rwlock_init
  3. 访问临界资源(读/写操作)前对读写锁进行加锁 pthread_rwlock_rdlock&pthread_rwlock_tryrdlock&pthread_rwlock_wrlock&pthread_rwlock_trywrlock
  4. 访问临界资源后进行解锁 pthread_rwlock_unlock
  5. 销毁读写锁变量 pthread_rwlock_destroy

!----

在Linux中读写锁的解锁,不区分解写锁还是读锁,加锁与解锁配对出现,为代码中距离最近的加锁操作进行解锁。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值