一文搞懂多线程中各个难点
2.3线程注意点
1、线程ID是进程地址空间内的一个地址, 要在同一个线程组内进行线程之间的比较才有意义。不同线程组内的两个线程, 哪怕两者的pthread_t值是一样的, 也不是同一个线程。
2、线程ID就有可能会被复用:
1、线程退出。
2、线程组的其他线程对该线程执行了pthread_join, 或者线程退出前将分离状态设置为已分离。
3、再次调用pthread_create创建线程。
2.4线程创建出来的默认值
线程创建的第二个参数是pthread_attr_t类型的指针, pthread_attr_init函数会将线程的属性重置成默认值。
线程属性及默认值:
如果确实需要很多的线程, 可以调用接口来调整线程栈的大小:
#include
线程终止,但进程不会终止的方法:
1、入口函数的return返回,线程就退出了
2、线程调用pthread_exit(NULL),谁调用谁退出
#include
void pthread_exit(void *retval);
参数:retval是返回信息,”临终遗言“,可以给可以不给
该变量不能使用临时变量。
可使用:全局变量、堆上开辟的空间、字符串常量。
pthread_exit和线程启动函数(start_routine) 执行return是有区别的。在start_routine中调用的任何层级的函数执行pthread_exit() 都会引发线程退出, 而return, 只能是在start_routine函数内执行才能导致线程退出。
3、其它线程调用了pthread_cancel函数取消了该线程
int pthread_cancel(pthread_t thread);
thread:线程标识符
调用该函数的执行流可以取消其它线程,但是需要知道其它线程的线程标识符,也可以执行流自己取消自己,传入自己的线程标识符。
如果线程组中的任何一个线程调用了exit函数, 或者主线程在main函数中执行了return语句, 那么整个线程组内的所有线程都会终止。
4.线程等待4.1线程等待接口#include
调用该函数,该执行流在等待线程退出的时候,该执行流是阻塞在pthread_joind当中的。
4.2线程等待和进程等待的不同
第一点不同之处是进程之间的等待只能是父进程等待子进程, 而线程则不然。线程组内的成员是对等的关系, 只要是在一个线程组内, 就可以对另外一个线程执行连接(join) 操作。
第二点不同之处是进程可以等待任一子进程的退出 , 但是线程的连接操作没有类似的接口, 即不能连接线程组内的任一线程, 必须明确指明要连接的线程的线程ID。
pthread_join()错误码:
4.3为什么要等待退出的线程?
如果不连接已经退出的线程, 会导致资源无法释放。所谓资源指的又是什么呢?
1、已经退出的线程, 其空间没有被释放, 仍然在进程的地址空间之内。
2、新创建的线程, 没有复用刚才退出的线程的地址空间。
如果不执行连接操作, 线程的资源就不能被释放, 也不能被复用, 这就造成了资源的泄漏。
纵然调用了pthread_join, 也并没有立即调用munmap来释放掉退出线程的栈, 它们是被后建的线程复用了。释放线程资源的时候, 若进程可能再次创建线程, 而频繁地munmap和mmap会影响性能, 所以将该栈缓存起来, 放到一个链表之中, 如果有新的创建线程的请求, 会首先在栈缓存链表中寻找空间合适的栈, 有的话, 直接将该栈分配给新创建的线程。
例:
#include
默认情况下, 新创建的线程处于可连接(Joinable) 的状态, 可连接状态的线程退出后, 需要对其执行连接操作, 否则线程资源无法释放, 从而造成资源泄漏。
如果其他线程并不关心线程的返回值, 那么连接操作就会变成一种负担:你不需要它, 但是你不去执行连接操作又会造成资源泄漏。这时候你需要的东西只是:线程退出时, 系统自动将线程相关的资源释放掉, 无须等待连接。
可以是线程组内其他线程对目标线程进行分离, 也可以是线程自己执行pthread_detach函数。
线程的状态之中, 可连接状态和已分离状态是冲突的, 一个线程不能既是可连接的, 又是已分离的。因此, 如果线程处于已分离的状态, 其他线程尝试连接线程时, 会返回EINVAL错误。
pthread_detach错误码:
注意:这里的已分离不是指线程失去控制,不归线程组管,而是指线程退出后,系统会自动释放线程资源。若是线程组内的任意线程执行了exit函数,即使是已分离的线程,也仍会收到影响,一并退出。
6.线程安全
线程安全中涉及到的概念:
临界资源:多线程中都能访问到的资源
临界区:每个线程内部,访问临界资源的代码,就叫临界区6.1什么是线程不安全?
多个线程访问同一块临界资源,导致资源产生二义性的现象。
6.1.1举一个例子
假设现在有两个线程A和B,单核CPU的情况下,此时有一个int类型的全局变量为100,A和B的入口函数都要对这个全局变量进行–操作。
线程A先拿到CPU资源后,对全局变量进行–操作并不是原子性操作,也就是意味着,A在执行–的过程中有可能会被打断。假设A刚刚将全局变量的值读到寄存器当中,就被切换出去了,此时程序计数器保存了下一条执行的指令,上下文信息保存寄存器中的值,这两个东西是用来线程A再次拿到CPU资源后,恢复现场使用的。
此时,线程B拿到了CPU资源,对全局变量进行了–操作,并且将100减为了99,回写到了内存中。
A再次拥有了CPU资源后,恢复现场,继续往下执行,从寄存器中读到的值仍为100,减完之后为99,回写到内存中为99。
上述例子中,线程A和B都对全局变量进行了–操作,全局变量的值应该变为98,但程序现在实际的结果为99,所以这就导致了线程不安全。
6.2如何解决线程不安全现象?
解决方案只需做到下述三点即可:
1、代码必须要有互斥的行为:当一个线程正在临界区中执行时, 不允许其他线程进入该临界区中。
2、如果多个线程同时要求执行临界区的代码, 并且当前临界区并没有线程在执行, 那么只能允许一个线程进入该临界区。
3、如果线程不在临界区中执行, 那么该线程不能阻止其他线程进入临界区。
则本质上,我们需要对该临界区加一把锁:
锁是一个很普遍的需求, 当然用户可以自行实现锁来保护临界区。但是实现一个正确并且高效的锁非常困难。纵然抛下高效不谈, 让用户从零开始实现一个正确的锁也并不容易。正是因为这种需求具有普遍性, 所以Linux提供了互斥量。
6.3互斥量接口
6.3.1互斥量的初始化
1、静态分配:
#include
2、动态分配:
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
调用int pthread_mutex_init()函数后,互斥量是处于没有加锁的状态。
6.3.2互斥量的销毁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
注意:
1、使用PTHREAD_MUTEX_INITIALIZER初始化的互斥量无须销毁。
2、不要销毁一个已加锁的互斥量, 或者是真正配合条件变量使用的互斥量。
3、已经销毁的互斥量, 要确保后面不会有线程再尝试加锁。
当互斥量处于已加锁的状态, 或者正在和条件变量配合使用, 调用pthread_mutex_destroy函数会返回EBUSY错误码。
6.3.3互斥量的加锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct timespec *restrict abs_timeout);
第一个接口:int pthread_mutex_lock(pthread_mutex_t *mutex);
1、该接口是阻塞加锁接口。
2、mutex为传入互斥锁变量的地址
3、如果mutex当中的计数器为1,pthread_mutex_lock接口就返回了,表示加锁成功,同时计数器当中的值会被更改为0.
4、如果mutex当中的计数器为0,pthread_mutex_lock接口就阻塞了,pthread_mutex_lock接口没有返回了,阻塞在函数内部,直到加锁成功
第二个接口:int pthread_mutex_trylock(pthread_mutex_t *mutex);
1、该接口为非阻塞接口
2、mutex中计数器为1时,加锁成功,计数器置为0,然后返回
3、mutex中计数器为0时,加锁失败,但也会返回,此时加锁是失败状态,一定不要去访问临界资源
4、非阻塞接口一般都需要搭配循环来使用。
第三个接口:int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct timespec *restrict abs_timeout);
1、带有超时时间的加锁接口
2、不能直接获取互斥锁的时候,会等待abs_timeout时间
3、如果在这个时间内加锁成功了,直接返回,不需要再继续等待剩余的时间,并且表示加锁成功
4、如果超出了该时间,也返回了,但是加锁失败了,需要循环加锁
上述三个加锁接口,第一个接口用的最多。
最新活动更多
-
11月20日火热报名中>> 2024 智能家居出海论坛
-
11月28日立即报名>>> 2024工程师系列—工业电子技术在线会议
-
12月19日立即报名>> 【线下会议】OFweek 2024(第九届)物联网产业大会
-
即日-12.26火热报名中>> OFweek2024中国智造CIO在线峰会
-
即日-2025.8.1立即下载>> 《2024智能制造产业高端化、智能化、绿色化发展蓝皮书》
-
精彩回顾立即查看>> 【在线会议】多物理场仿真助跑新能源汽车
推荐专题
发表评论
请输入评论内容...
请输入评论/评论长度6~500个字
暂无评论
暂无评论