距离上一次利用高并发技术实现360度行车记录仪功能已经过去半年了。开始写一系列关于系统编程和网络编程内容进行总结。
温故而知新,欢迎大家讨论学习。
ps -efj
的输出实例,内核守护进程的名字出现在方括号中,大致输出如下:主要是理解一些概念,重点参考一下文献。
参考文献(非常重要)
1 掩码+进程组+会话的描述
2 dev/null是什么
3标准输入 标准输出 标准错误
#include
#include
#include
#include
#include
#include
#include
#include
void sys_err(const char *str)
{
perror(str);
exit(1);
}
int main(int argc, char *argv[])
{
pid_t pid;
int ret, fd;
pid = fork();
if (pid > 0) // 父进程终止
exit(0);
pid = setsid(); //创建新会话
if (pid == -1)
sys_err("setsid error");
ret = chdir("/home/itcast/28_Linux"); // 改变工作目录位置
if (ret == -1)
sys_err("chdir error");
umask(0022); // 改变文件访问权限掩码
close(STDIN_FILENO); // 关闭文件描述符 0标准输入 //因为0 1 2 都是进程启动默认启动的
fd = open("/dev/null", O_RDWR); // fd --> 0
if (fd == -1)
sys_err("open error");
dup2(fd, STDOUT_FILENO); // 重定向 stdout和stderr
dup2(fd, STDERR_FILENO);
while (1); // 模拟 守护进程业务.
return 0;
}
轻量级进程(light-weight process),也有 PCB,创建线程使用的底层函数和进程一样,都是 clone
进程可以蜕变成线程
线程可看做寄存器和栈的集合
在 linux 下,线程最是小的执行单位;进程是最小的分配资源单位
参考:《Linux 内核源代码情景分析》
对于进程来说,相同的地址(同一个虚拟地址)在不同的进程中,反复使用而不冲突。原因是他们虽虚拟址一样,但,页目录、页表、物理页面各不相同。相同的虚拟址,映射到不同的物理页面内存单元,最终访问不同的物理页
面。
但!线程不同!两个线程具有各自独立的 PCB,但共享同一个页目录,也就共享同一个页表和物理页面。所以两个 PCB 共享一个地址空间。
实际上,无论是创建进程的 fork,还是创建线程的 pthread_create,底层实现都是调用同一个内核函数 clone。
如果复制对方的地址空间,那么就产出一个“进程”;如果共享对方的地址空间,就产生一个“线程”。
因此:Linux 内核是不区分进程和线程的。只在用户层面上进行区分。所以,线程所有操作函数 pthread_* 是库函数,而非系统调用
优点:
缺点:
编译的时候记得后面
-l pthread
毕竟第三方库实现
作用:
获取线程 ID。其作用对应进程中 getpid() 函数。
pthread_t pthread_self(void);
//成功返回本线程id创建一个新线程,起作用,对应进程中fork()函数。
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg)
; 成功返回0,失败返回errno主线程结束,但是进程地址空间还在,其他子线程正常执行. 如果main中return,由于其他子线程函数空间是在main函数里面,所以main不能提前结束。sleep就是让子进程中的函数执行完。(重)
注意 pthread_create 第四个参数的传递 ,以及后面的强制转换。可以先去体会一下错误写法(下下方的例子)
# include
# include
# include
# include
# include
# include
void* fun(void* arg)
{
int i=(int)arg;
sleep(i);
printf("I am %dth thread: pid=%d,tid=%lu\n",i+1,getpid(),pthread_self());
return NULL;
}
void sys_err(const char*str)
{
perror(str);
exit(1);
}
int main()
{
int i;
int ret;
pthread_t tid;
for(i=0;i<5;i++)
{
ret = pthread_create(&tid,NULL,fun,(void*)i);
if(ret!=0)
{
sys_err("pthread_creat error");
}
}
sleep(i);
return 0;
}
从结果很容易看出i的打印出现的问题,那是因为当我们从子进程(函数内)通过i的地址取值的时候。这个i的地址是在父进程栈区。这片地址存放的值i可能已经发生了变化。毕竟父进程也一直在执行
# include
# include
# include
# include
# include
# include
void* fun(void* arg)
{
int i=*((int*)arg);
sleep(i);
printf("I am %dth thread: pid=%d,tid=%lu\n",i+1,getpid(),pthread_self());
return NULL;
}
void sys_err(const char*str)
{
perror(str);
exit(1);
}
int main()
{
int i;
int ret;
pthread_t tid;
for(i=0;i<5;i++)
{
ret = pthread_create(&tid,NULL,fun,(void*)&i);
if(ret!=0)
{
sys_err("pthread_creat error");
}
}
sleep(i);
return 0;
}
作用:
将单个线程退出
void pthread_exit(void *retval);
参数:retval 表示线程退出状态,通常传 NULL直接说结论:
线程中,禁止使用 exit 函数,会导致进程内所有线程全部退出。
return 在子线程是没有问题的,他是退出到函数调用的位置,也算是线程结束。毕竟线程也就是一个函数调用罢了。
pthread_exit 线程退出而不是进程退出切记。
举个例子放下面,注释部分自己去掉试试就懂了主控线程退出时不能 return 或 exit。(重)
# include
# include
# include
# include
# include
# include
void* fun(void* arg)
{
int i=(int)arg;
if(i==2)
{
//return 0;
//exit(0);
//pthread_exit(NULL);
}
sleep(i);
printf("I am %dth thread: pid=%d,tid=%lu\n",i+1,getpid(),pthread_self());
return NULL;
}
void sys_err(const char*str)
{
perror(str);
exit(1);
}
int main()
{
int i;
int ret;
pthread_t tid;
for(i=0;i<5;i++)
{
ret = pthread_create(&tid,NULL,fun,(void*)i);
if(ret!=0)
{
sys_err("pthread_creat error");
}
}
sleep(i);
return 0;
}
主线程结束,但是进程地址空间还在,其他子线程正常执行. 如果main中return ,由于其他子线程函数空间是在main函数里面,所以main不能提前结束。
# include
# include
# include
# include
# include
# include
void* fun(void* arg)
{
int i=(int)arg;
if(i==2)
{
//return 0;
//exit(0);
//pthread_exit(NULL);
}
sleep(i);
printf("I am %dth thread: pid=%d,tid=%lu\n",i+1,getpid(),pthread_self());
return NULL;
}
void sys_err(const char*str)
{
perror(str);
exit(1);
}
int main()
{
int i;
int ret;
pthread_t tid;
for(i=0;i<5;i++)
{
ret = pthread_create(&tid,NULL,fun,(void*)i);
if(ret!=0)
{
sys_err("pthread_creat error");
}
}
pthread_exit(NULL);
}
作用:
阻塞等待线程退出,获取线程退出状态 其作用,对应进程中 waitpid() 函数
补充:任意线程得到其他线程的pid都可以回收,没有父线程回收子线程的说法。而进程需要父进程回收子进程。
int pthread_join(pthread_t thread, void **retval);
成功:0;失败:错误号#include
#include
#include
#include
typedef struct {
int a;
int b;
} exit_t;
void *tfn(void *arg)
{
exit_t *ret;
ret = malloc(sizeof(exit_t));
ret->a = 100;
ret->b = 300;
pthread_exit((void *)ret);
}
int main(void)
{
pthread_t tid;
exit_t *retval;
pthread_create(&tid, NULL, tfn, NULL);
/*调用pthread_join可以获取线程的退出状态*/
pthread_join(tid, (void **)&retval); //wait(&status);
printf("a = %d, b = %d \n", retval->a, retval->b);
return 0;
}
作用:
实现线程分离,线程结束后,自动释放资源。无需pthread_join() 回收资源。
int pthread_detach(pthread_t thread);
成功:0;失败:错误号线程分离状态:指定该状态,线程主动与主控线程断开关系。线程结束后,其退出状态不由其他线程获取,而直接自己自动释放。网络、多线程服务器常用。
进程若有该机制,将不会产生僵尸进程。僵尸进程的产生主要由于进程死后,大部分资源被释放,一点残留资源仍存于系统中,导致内核认为该进程仍存在。
也可使用 pthread_create 函数参 2(线程属性)来设置线程分离。
符合pthread_detach作用,分离独立。主线程无法再等待回收子线程资源。
# include
# include
# include
# include
# include
# include
void* fun(void* arg)
{
int i=(int)arg;
printf("I am %dth thread: pid=%d,tid=%lu\n",i+1,getpid(),pthread_self());
sleep(10);
return NULL;
}
void sys_err(const char*str)
{
perror(str);
exit(1);
}
int main()
{
int i=1;
int ret;
pthread_t tid;
ret = pthread_create(&tid,NULL,fun,(void*)i);
if(ret!=0)
{
sys_err("pthread_creat error");
}
ret=pthread_detach(tid);
if(ret==0)
{
printf("success pthread_detach\n");
}
ret = pthread_join(tid,NULL);
if(ret!=0)
{
printf("pthread_join error\n");
}
pthread_exit(NULL);
}
作用:
杀死(取消)线程 其作用,对应进程中 kill() 函数。
int pthread_cancel(pthread_t thread);
成功:0;失败:错误号线程的取消并不是实时的,而有一定的延时。需要等待线程到达某个取消点(检查点)。
取消点:是线程检查是否被取消,并按请求进行动作的一个位置。通常是一些系统调用
creat,open,pause, close,read,write… 执行命令 man 7 pthreads
可以查看具备这些取消点的系统调用列表。也可参阅 APUE.12.7 取消选项小节。
可粗略认为一个系统调用(进入内核)即为一个取消点。如线程中没有取消点,可以通过调用pthread_testcancel
函数自行设置一个取消点。
进程 | 线程 |
---|---|
fork | pthread_create |
exit | pthread_exit |
wait | pthread_join |
kill | pthread_cancel |
getpid | pthread_self |
如有错误欢迎指出…