1.经典的面试题:用 4 个线程疯狂的打印 abcd 持续 5 秒钟,但是要按照顺序打印,不能是乱序的。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#define THRNUM 4
static pthread_mutex_t mut[THRNUM];
static int next(int a)
{
if(a+1 == THRNUM)
return 0;
return a+1;
}
static void *thr_func(void *p)
{
int n = (int)p;
int ch = n + 'a';
while(1)
{
pthread_mutex_lock(mut+n);
write(1,&ch,1);
pthread_mutex_unlock(mut+next(n));
}
pthread_exit(NULL);
}
int main()
{
int i,err;
pthread_t tid[THRNUM];
for(i = 0 ; i < THRNUM ; i++)
{
pthread_mutex_init(mut+i,NULL);
pthread_mutex_lock(mut+i);
err = pthread_create(tid+i,NULL,thr_func,(void *)i);
if(err)
{
fprintf(stderr,"pthread_create():%s\n",strerror(err));
exit(1);
}
}
pthread_mutex_unlock(mut+0);
alarm(5);
for(i = 0 ; i < THRNUM ; i++)
pthread_join(tid[i],NULL);
exit(0);
}
上面这段代码是通过多个互斥量实现了一个锁链的结构巧妙的实现了要求的效果。
首先定义 4 个互斥量,然后创建 4 个线程,每个互斥量对应一个线程,每个线程负责打印一个字母。4 个线程刚刚被创建好时,4 把锁都处于锁定状态,4 个线程全部都阻塞在临界区之外,等 4 个线程全部都创建好之后解锁其中一把锁。被解锁的线程首先将自己的互斥量上锁,然后打印字符再解锁下一个线程对应的互斥量,然后再次等待自己被解锁。如此往复,使 4 个线程有条不紊的循环执行 锁定自己 --- 打印字符 -- 解锁下一个线程 的步骤,这样打印到控制台上的 abcd 就是有序的了。
从上面的例子可以看出来:互斥量限制的是一段代码能否执行,而不是一个变量或一个资源。上面的代码虽然使用锁链巧妙的完成了任务,但是它的实现方式并不是最漂亮的,更好的办法是条件变量。
这道题真正的考点其实是使用互斥量 + 条件变量的方式来实现。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#define THRNUM 4
static pthread_mutex_t mut_num = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond_num = PTHREAD_COND_INITIALIZER;
static int num = 0;
static int next(int a)
{
if(a+1 == THRNUM)
return 0;
return a+1;
}
static void *thr_func(void *p)
{
int n = (int)p;
int ch = n + 'a';
while(1)
{
// 先抢锁,能抢到锁就可以获得打印的机会
pthread_mutex_lock(&mut_num);
while(num != n)
{
// 抢到锁但是发现不应该自己打印,那就释放锁再出让调度器,让别人尝试抢锁
pthread_cond_wait(&cond_num,&mut_num);
}
write(1,&ch,1);
num = next(num);
/*
* 自己打印完了,通知别人你们抢锁吧
* 因为不知道下一个应该运行的线程是谁,
* 所以采用惊群的方式把它们全都唤醒,
* 让它们自己检查是不是该自己运行了。
*/
pthread_cond_broadcast(&cond_num);
pthread_mutex_unlock(&mut_num);
}
pthread_exit(NULL);
}
int main()
{
int i,err;
pthread_t tid[THRNUM];
for(i = 0 ; i < THRNUM ; i++)
{
// 直接启动 4 个线程,让它们自己判断自己是否应该运行,而不用提前锁住它们
err = pthread_create(tid+i,NULL,thr_func,(void *)i);
if(err)
{
fprintf(stderr,"pthread_create():%s\n",strerror(err));
exit(1);
}
}
alarm(5);
for(i = 0 ; i < THRNUM ; i++)
pthread_join(tid[i],NULL);
exit(0);
}
2.如果面试的时候问你:处理常规任务时,是采用多线程比较快还是采用多进程比较快?
如果只回答多线程比较快,那么你工资一定多不了。
应该回答常规情况下是多线程较快,因为多进程需要重新布置进程的执行空间,还需要进行数据拷贝以及部分配置,所以会比创建线程慢xx倍。
不要只回答一个大方向就完事了,而是要量化你的答案,这样才能体现出来你在平时学习工作中很注重这些细节问题。
3.一个进程最多能创建多少个线程
一个进程能够创建多少个线程呢?主要受两个因素影响,一个是 PID 耗尽,一个是在之前的 C 程序地址空间布局时的阴影区域被栈空间占满了 。
PID 看上去是进程 ID,内核的最小执行单元其实是线程,实际上是线程在消耗 PID。一个系统中的线程可以有很多,所以 PID 被耗尽也是有可能的。
使用 ulimit(1) 命令可以查看栈空间的大小,阴影区剩余空间的大小 / 栈空间的大小 == 就是能创建的线程数量。
可以自己写个程序测试一下一个进程最多能够创建多少个线程,然后使用 ulimit(1) 命令修改栈的大小再测试几次,看看能有什么发现。代码很简单。