Q1. Pthread 属性有哪些?如何创建和销毁?
属性主要有detached and joinable state,scheduling inheritance,policy ,parameters,contention scope, stack size,address, guard size,
pthread_attr_t attr;
pthread-attr-init();
pthread-attr-destroy();
pthread_mutexattr_t;
pthread_condattr_t;
Q2. 创建线程和结束线程的方法
创建线程
参数1:线程地址;
参数2:属性结构体地址;
参数3:函数指针,返回指针,可以返回多个参数;
参数4:传入函数参数,为了提高通用性,转换为(void *),使用时再由类型符进行转换;
结束线程
- main() 函数先结束了,而且
main() 自己没有调用 pthread_exit
来等所有线程完成任务.
- 子线程
- 线程函数 return,也就是线程的任务已经完成;
- 线程调用了 pthread_exit 函数;pthread_exit(void * retval) 可以返回线程返回状态
- 其他线程调用 pthread_cancel 结束这个线程;
- 进程调用 exec() or exit(),结束了;
main函数若未添加 pthread_exit,可能会 main 运行结束,其他线程被强制终止。
Q3. 线程阻塞的意义
阻塞是线程之间同步的一种方法
pthread_join 函数会让调用它的线程等待 threadid 线程运行结束之后再运行。比如在主线程中调用 pthread_join,main线程的继续运行,依赖于threadid 运行完成。
Q4. 显示设置线程为joinable的步骤
The typical 4 step process is:
- Declare a pthread attribute variable of the
pthread_attr_t
data type
- Initialize the attribute variable with
pthread_attr_init()
- Set the attribute detached status with
pthread_attr_setdetachstate()
- When done, free library resources used by the attribute with
pthread_attr_destroy()
Q5. 僵尸线程是什么?如何避免其出现
一个没有被分离的线程,在退出时,系统会保留它的虚拟内存,包括他们的堆栈和其他系统资源,这种线程被称为“僵尸线程”。更多关于线程两种状态的描述见[这里]。
大量的僵尸线程会占用大量的系统资源,导致运行异常,需要正确的回收系统资源。回收系统程序需要如下两个函数
有关联
:pthread_join()
函数,以阻塞的方式等待thread指定的线程结束。当函数返回时,被等待线程的资源被收回。如果线程已经结束,那么该函数会立即返回。并且thread指定的线程必须是joinable的;
phread_detach
函数,回收创建时detachstate
属性设置为PTHREAD_CREATE_JOINABLE
的线程的存储空间。
两者差别:
pthread_join()
函数会阻塞线程(线程有次序),而 pthread_detach()
函数不会阻塞线程(线程无次序)。
方法1:线程A退出后,线程B调用pthread_join来主动释放线程A的资源。pthread_join可能发生阻塞。
方法2:创建线程前,利用
pthread_attr_setdetachstate
将线程设为detached
,这样线程退出时,系统自动回收线程资源。方法3:主线程中,创建线程后,用
pthread_detach
将其设置为detached。方法4:子线程中,直接用
pthread_detach(pthread_self())
,在线程任务中,用pthread_exit中止。参考链接:
Q6. 堆栈管理 (Stack Management)是什么?为什么需要它,如何使用它?
POSIX 标准并未指定线程栈的大小,安装和可移植的程序,不应该依赖于默认的栈限制,需要用相应的堆栈管理API分配足够的栈空间(pthread_attr_setstacksize) 和指定栈存放的内存区域(pthread_attr_setstackaddr)。否则会导致超出默认的栈大小,造成程序终止或者数据损坏。
Q7. 互斥锁和条件变量的作用和区别?
多线程同时访问共享资源,会出现资源竞争,导致数据时序混乱的情况,为了解决这种竞争,通过互斥锁和条件变量等方式,实现同步运行(排队运行)。同时,基于互斥锁和条件变量还可以实现事件和semphore 等方式。
项 | 互斥锁 | 条件变量 |
比喻 | 独木桥 | 交通灯 |
对象+操作 | 在单线程内部为临界区操作上锁 | 类似于通过信号的产生和等待控制多线程之间的通讯 |
类别 | 上锁和解锁 | 可以实现自定义条件 |
作用机制 | FIFO队列方式,释放后,队头线程获得锁 | 和互斥锁共用,pthread_cond_wait(&m_cond,&m_mutex);
1. 首先互斥对象解锁,进入阻塞状态,调用线程不会占用CPU周期,一直睡眠直到条件发生
2. 条件触发,线程苏醒
3. 重新锁定子线程锁,返回主线程继续执行 |
Q8. 为何存在mutex还是需要 condition variables
条件变量用于某个线程需要在
某种条件成立时
才去保护它将要操作的临界区
,这种情况从而避免了线程不断轮询检查
该条件是否成立而降低效率的情况,这是实现了效率提高。在条件满足时,自动退出阻塞,再加锁进行操作。首先,举个例子:在应用程序中有4个进程thread1,thread2,thread3和thread4,有一个int类型的全局变量iCount。iCount初始化为0,thread1和thread2的功能是对iCount的加1,thread3的功能是对iCount的值减1,而thread4的功能是当iCount的值大于等于100时,打印提示信息并重置iCount=0。
如果使用互斥量,线程代码大概应是下面的样子:
在上面代码中由于thread4并不知道什么时候iCount会大于等于100,所以就会一直在循环判断,但是每次判断都要加锁、解锁(即使本次并没有修改iCount)。这就带来了问题一:CPU浪费严重。所以在代码中添加了sleep(),这样让每次判断都休眠一定时间。但这又带来的问题二:如果sleep()的时间比较长,导致thread4处理不够及时,等iCount到了很大的值时才重置。对于上面的两个问题,可以使用条件变量来解决。
首先看一下使用条件变量后,线程代码大概的样子:
需要注意的一点是在thread4中使用的while (iCount < 100),而不是if (iCount < 100)。这是因为在pthread_cond_singal()和pthread_cond_wait()返回之间有时间差,假如在时间差内,thread3又将iCount减到了100以下了,那么thread4在pthread_cond_wait()返回之后,显然应该再检查一遍iCount的大小,这就是while的用意,如果是if,则会直接往下执行,不会再次判断。
Q9. 条件变量模板,条件变量为何要和互斥锁共用
需和互斥锁共用,防止第一个condition signal 丢失
pthread_cond_signal即可以放在pthread_mutex_lock和pthread_mutex_unlock之间,也可以放在pthread_mutex_lock和pthread_mutex_unlock之后,但是各有有缺点。
之间:
缺点:在某下线程的实现中,会造成等待线程从内核中唤醒(由于cond_signal)然后又回到内核空间(因为cond_wait返回后会有原子加锁的 行为),所以一来一回会有性能的问题。但是在LinuxThreads或者NPTL里面,就不会有这个问题,因为在Linux 线程中,有两个队列,分别是cond_wait队列和mutex_lock队列, cond_signal只是让线程从cond_wait队列移到mutex_lock队列,而不用返回到用户空间,不会有性能的损耗。
所以在Linux中推荐使用这种模式。
之后:
优点:不会出现之前说的那个潜在的性能损耗,因为在signal之前就已经释放锁了
缺点:如果unlock和signal之前,有个低优先级的线程正在mutex上等待的话,那么这个低优先级的线程就会抢占高优先级的线程(cond_wait的线程),而这在上面的放中间的模式下是不会出现的。
参考资料
Q10. 范例:生产者消费者实现
Q11. 进程和线程的区别?
Process contain info | Thread contain info |
• Process ID, process group ID, user ID, and group ID | ㅤ |
• Environment | ㅤ |
• Working directory | ㅤ |
• Program instructions | ㅤ |
• Registers | Registers |
• Stack | Stack pointer, Scheduling properties (such as policy or priority) |
• Heap | ㅤ |
• File descriptors | Thread specific data. |
• Signal actions | Set of pending and blocked signals |
• Shared libraries | ㅤ |
Inter-process communication tools (such as message queues, pipes, semaphores, or shared memory) | ㅤ |