linux c编程:初识进程与线程
p { margin-bottom: 0.25cm; line-height: 120% }
(一) 认识进程
在Linux系统中,每一个进程都有自己的ID,就如同人的身份证一样。linux中有一个数据类pid_t。 该数据用来定义进程的ID。其实就是一个非负的整数
进程的状态:运行态,等待态,结束态,就绪,挂起和僵尸状态。进程就是在这几个状态间来回切换。
首先来看下如何创建新的进程,这里需要用到fork函数。使用fork函数需要用到<sys/types.h>和<unistd.h>头文件,函数的返回类型为pid_t。表示一个非负的整数。代码如下
void get_pid()
{
pid_t pid;
if ((pid=fork())<0)
{
printf("fork error\n");
exit(1);
}
else if(pid == 0)
{
printf("the pid is %d\n, gvar=%d,var=%d\n",pid,gvar,var);
printf("in the child process!\n");
}
else
{
printf("the pid is %d\n, gvar=%d,var=%d\n",pid,gvar,var);
printf("in the parent process!\n");
}
exit(0);
}
p { margin-bottom: 0.25cm; line-height: 120% }
执行结果:
the pid is 3738in the parent process!
the pid is 0in the child process!
从结果可以看到返回了两条打印,分别是父进程和子进程。原因在于调用fork函数后,就会出现分叉。在子进程中fork函数返回0, 在父进程中,fork函数返回子进程的ID。也就是fork函数会复制一个进程给子进程。为什么pid的值在父子进程中返回的值不一样呢。原因在于父子进程相当与链表,进程形成了链表,父亲进程的pid意味这指向子进程的id,但是子进程没有id,所以其pid=0
这样fork函数就是调用一次,返回两次。在开发过程中,可以根据返回值的不同,对父进程和子进程执行不同的代码。我们来把代码变动下来看下父进程和子进程的关系。增加了一个全局变量gvar和局部变量var。
int gvar=2;
void get_pid()
{
pid_t pid;
int var=5;
if ((pid=fork())<0)
{
printf("fork error\n");
exit(1);
}
else if(pid == 0)
{
gvar--;
var++;
printf("the pid is %d\n, gvar=%d,var=%d\n",pid,gvar,var);
printf("in the child process!\n");
}
else
{
printf("the pid is %d\n, gvar=%d,var=%d\n",pid,gvar,var);
printf("in the parent process!\n");
}
exit(0);
}
p { margin-bottom: 0.25cm; line-height: 120% }
运行结果,在子进程中,对gvar和var分别进行减一和加一的操作。但是在父进程中,gvar和var仍然是原值且并没有被改动。原因在于fork函数胡复制父进程的所有资源,包括内存等。因此父子进程属于不同的内存空间。当修改的时候自然没有同步。
the pid is 4694
, gvar=2,var=5
in the parent process!
the pid is 0
, gvar=1,var=6
in the child process!
除了fork外,还有一个vfork函数,和fork一样都是系统调用函数。两者的区别在于vfork在创建子进程的时候不会复制父进程的所有资源,父子进程共享地址空间。因此子进程中修改的所有变量在父进程也会被修改,因为同属一个地址空间。
int get_pid__vfork()
{
pid_t pid;
int var =5;
printf("process id:%d\n",getpid());
printf("gvar=%d var=%d\n",gvar,var);
if((pid=vfork())<0)
{
printf("error");
return 1;
}
else if(pid == 0)
{
gvar--;
var++;
printf("The child process id:%d\n gvar=%d var=%d\n",getpid(),gvar,var);
exit(0);
}
else
{
printf("the pareent process id:%d\n gvar=%d var=%d\n",getpid(),gvar,var);
return 0;
}
}
p { margin-bottom: 0.25cm; line-height: 120% }
执行结果:
process id:4821
gvar=2 var=5
The child process id:4822
gvar=1 var=6
the pareent process id:4821
gvar=1 var=6
在fork和vfork中,子进程和父进程都是运行同样的代码。那么如果想让子进程执行不同的操作。就需要用到execv函数
在test1.c中的代码:
p { margin-bottom: 0.25cm; line-height: 120% }
void main(int argc,char* argv[])
{
execve("test2",argv,environ);
}
test2.c中的代码
int main()
{
puts("welcome!");
return 0;
}
p { margin-bottom: 0.25cm; line-height: 120% }
首先编译这2个文件,得到两个可执行的文件test1和test2。然后运行./test1. 得到的结果如下:test1.c中执行了test2.c的代码。
root@zhf-linux:/home/zhf/zhf/c_prj# ./test1
welcome!
(二)进程等待:
进程等待就是为了同步父进程和子进程。等待调用wait函数,wait函数的工作原理是首先判断子进程是否存在,如果创建失败,子进程不存在,则会直接退出进程。并且提示相关错误信息。如果创建成功,wait函数会将父进程挂起,直到子进程结束,并且返回结束时的状态和最后结束的子进程PID。来看下具体的代码实现。
oid exit_s(int status)
{
if(WIFEXITED(status)){
printf("normal exist,status=%d\n",WEXITSTATUS(status));
}
else if(WIFSIGNALED(status)){
printf("signal exit!status=%d\n",WTERMSIG(status));
}
}
void wait_function_test()
{
pid_t pid,pid1;
int status;
int ret;
int pr;
if((pid=fork())<0)
{
printf("child process error!\n");
exit(0);
}
else if(pid == 0)
{
printf("the pid of child is %d\n",getpid());
printf("The child process\n");
sleep(3);
exit(2);
}
else
{
printf("the pid of parent is %d\n",getpid());
printf("I am waiting for child process to exit\n");
pr=wait(&status);
if (pr > 0){
printf("I catched a child process with pid of %d\n",pr);
}
exit_s(status);
}
}
p { margin-bottom: 0.25cm; line-height: 120% }
在子进程中调用sleep(3)睡眠3秒钟。只有子进程从睡眠中苏醒过来,才能正常退出并被父进程捕捉到。在此期间父进程会继续等待下去。在wait函数中会将子进程的状态保存在status中
在 exit_s中调用了几个宏:
WIFEXITED:当子进程正常退出时,返回真值
WEXITSTATUS:返回子进程正常退出时的状态。只有当WIFEXITED为真值的时候。
WTERMSIG:用于子进程被信号终止的情况。返回此信号类型
执行结果如下,可以看到在父进程等到3秒后,子进程开始退出。然后被父进程捕获到。
the pid of parent is 5063
I am waiting for child process to exit
the pid of child is 5064
The child process
I catched a child process with pid of 5064
normal exist,status=2
那如果子进程是异常退出的,会是什么样的情况呢。
void wait_function_test_for_childerror()
{
pid_t pid,pid1;
int status;
int ret;
int pr;
if((pid=fork())<0)
{
printf("child process error!\n");
exit(0);
}
else if(pid == 0)
{
pid1=getpid();
printf("the pid of child is %d\n",pid1);
printf("The child process\n");
kill(pid1,9);
}
else
{
printf("the pid of parent is %d\n",getpid());
printf("I am waiting for child process to exit\n");
pr=wait(&status);
printf("the pid is %d\n",pid);
printf("the pr value is %d\n",pr);
if (pr != pid)
{
printf("parent process wait error\n");
}
exit_s(status);
}
}
p { margin-bottom: 0.25cm; line-height: 120% }
在子进程中用kill(pid,9)的方法来杀死进程。其中kill的用法参考kill -l命令。结果如下。
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
执行结果如下:可以看到不再是normal exit而是signal exit,并且状态码也从2变成了9
I am waiting for child process to exit
the pid of child is 5218
The child process
I catched a child process with pid of 5218
signal exit!status=9
(三)线程
首先来看下线程和进程的区别。
进程是资源分配的最小单位,每个进程都是有独立的内存空间
线程是程序执行的最小单位。多个线程共享同一个内存空间。
一个进程由几个线程组成(拥有很多相对独立的执行流的用户程序共享应用程序的大部分数据结构),线程与同属一个进程的其他的线程共享进程所拥有的全部资源。
总的来说就是进程有独立的地址空间,线程没有单独的地址空间,同一进程内的所有线程共享内存空间
那么线程相对于进程的优势是什么呢:
1
线程不需要额外的内存申请
2
线程共享进程内的数据,访问数据方便。而进程则需要通过通信的方式进行
进程使用ps的方式查看,线程则可以通过top的方式进行查看。还可以通过top
-p pid的方式进行某个进程内的线程查看。

p { margin-bottom: 0.25cm; line-height: 120% }
来看一个创建线程的例子:
void *mythread1(void)
{
int i;
for(i=0;i<5;i++)
{
printf("This is the first thread\n");
sleep(1);
}
}
void *mythread2(void)
{
int i;
for(i=0;i<5;i++)
{
printf("This is the second thread\n");
sleep(1);
}
}
void main(int argc,char* argv[])
{
int ret=0;
pthread_t id1,id2;
pthread_create(&id1,NULL,(void *)mythread1,NULL);
pthread_create(&id2,NULL,(void *)mythread2,NULL);
pthread_join(id1,NULL);
pthread_join(id2,NULL);
}
p { margin-bottom: 0.25cm; line-height: 120% }
mythread1和mythread2两个函数分别属于不同的线程。pthread_t 是线程的id.。pthread_create是创建线程的函数。其中的第一个为线程id。第二个参数指线程的属性,这里设置位空。第三个参数:线程运行函数的起始地址。第四个参数:运行函数的参数。这里也设置位空。
但是在用gcc调试的时候出现如下错误,提示找不到线程的相关函数。原因在于pthread的库不是Linux系统的库,所以在进行编译的时候要加上-lpthread,否则编译不过
root@zhf-linux:/home/zhf/zhf/c_prj#
gcc -g -o test1 test1.c
/tmp/ccH40hx1.o:
In function `main':
/home/zhf/zhf/c_prj/test1.c:188:
undefined reference to `pthread_create'
/home/zhf/zhf/c_prj/test1.c:189:
undefined reference to `pthread_create'
/home/zhf/zhf/c_prj/test1.c:190:
undefined reference to `pthread_join'
/home/zhf/zhf/c_prj/test1.c:191:
undefined reference to `pthread_join'
换成如下就可以正常编译了:
root@zhf-linux:/home/zhf/zhf/c_prj#
gcc -o test1 test1.c -lpthread
root@zhf-linux:/home/zhf/zhf/c_prj#
./test1
执行结果如下:两个线程在交替的运行
This
is the first thread
This
is the second thread
This
is the second thread
This
is the first thread
This
is the first thread
This
is the second thread
This
is the first thread
This
is the second thread
This
is the first thread
This
is the second thread
线程还有很多其他的操作,比如变量互斥,锁,同步,信号等,讲在后面的章节中详细介绍。
linux c编程:初识进程与线程的更多相关文章
- python并发编程之进程、线程、协程的调度原理(六)
进程.线程和协程的调度和运行原理总结. 系列文章 python并发编程之threading线程(一) python并发编程之multiprocessing进程(二) python并发编程之asynci ...
- linux高级编程基础系列:线程间通信
linux高级编程基础系列:线程间通信 转载:原文地址http://blog.163.com/jimking_2010/blog/static/1716015352013102510748824/ 线 ...
- (转)如何在Linux中统计一个进程的线程数
如何在Linux中统计一个进程的线程数 原文:http://os.51cto.com/art/201509/491728.htm 我正在运行一个程序,它在运行时会派生出多个线程.我想知道程序在运行时会 ...
- 【Linux 应用编程】进程管理 - 进程、线程和程序
基本概念 程序和进程的区别 程序是平台相关的二进制文件,只占用磁盘空间.编写完程序代码后,编译为可执行的二进制文件即可. 进程是运行中的程序,占用 CPU.内存等系统资源. 通过 Shell 命令,可 ...
- Linux多线程编程,为什么要使用线程,使用线程的理由和优点等
线程?为什么有了进程还需要线程呢,他们有什么区别?使用线程有什么优势呢?还有多线程编程的一些细节问题,(http://www.0830120.com)如线程之间怎样同步.互斥,这些东西将在本文中介绍. ...
- python 多线程编程之进程和线程基础概念
多线程编程 在多线程(multithreaded,MT)出现之前,计算机程序的执行都是由单个步骤序列组成的,该序列组合在主机的CPU中按照同步顺序执行.无论是任务本身需要按照步骤顺序执行,还是整个过程 ...
- Java并发编程:进程和线程之由来
Java多线程基础:进程和线程之由来 在前面,已经介绍了Java的基础知识,现在我们来讨论一点稍微难一点的问题:Java并发编程.当然,Java并发编程涉及到很多方面的内容,不是一朝一夕就能够融会贯通 ...
- java并发编程:进程和线程
java并发编程涉及到很多内容,当然也包括多线程,再次补充点相关概念 原文地址:http://www.cnblogs.com/dolphin0520/p/3910667.html 一.操作系统中为什么 ...
- Java并发编程:进程和线程之由来__进程让操作系统的并发性成为可能,而线程让进程的内部并发成为可能
转载自海子:http://www.cnblogs.com/dolphin0520/p/3910667.html Java多线程基础:进程和线程之由来 在前面,已经介绍了Java的基础知识,现在我们来讨 ...
- linux系统编程之进程(六):父进程查询子进程的退出,wait,waitpid
本节目标: 僵进程 SIGCHLD wait waitpid 一,僵尸进程 当一个子进程先于父进程结束运行时,它与其父进程之间的关联还会保持到父进程也正常地结束运行,或者父进程调用了wait才告终止. ...
随机推荐
- 图解虚数 - A Visual, Intuitive Gudie to Imaginary Numbers
这是一篇发表在 betterexplained 上的文章.它通过类比.图解的方式简明地介绍了虚数的意义. 作者:Kalid 原文:A Visual, Intuitive Gudie to Imagin ...
- Python爬虫从入门到放弃(二十)之 Scrapy分布式原理
关于Scrapy工作流程回顾 Scrapy单机架构 上图的架构其实就是一种单机架构,只在本机维护一个爬取队列,Scheduler进行调度,而要实现多态服务器共同爬取数据关键就是共享爬取队列. 分布式架 ...
- InputStreamReader读取文件出现乱码
当你读取一个文本文件时,如果是在电脑记事本上面写的,然后保存.使用InputStreamReader读取出来,并且是按照他的编码格式读取出来,发现还是存在乱码,比如文本内容是“你好”,读出来显示是“? ...
- Data truncation: Data too long for column 'gender' at row 1 出现的原因
创建数据库的代码如下: create database day15 ; use day15 ; create table customer( id varchar(100) primary key, ...
- Python - Seaborn可视化:图形个性化设置的几个小技巧
1 概述 在可视化过程中,经常会对默认的制图效果不满意,希望能个性化进行各种设置. 本文通过一个简单的示例,来介绍seaborn可视化过程中的个性化设置.包括常用的设置,如: 设置图表显示颜色 设置图 ...
- Mybatis分页插件PageHelper正确的使用方法(网上有2篇不够科学的文章)
今天下午在Mybatis项目中,实现分页.因为我是后加入项目中的,Leader用的是PageHelper这个组件,但是我在实际使用的过程中遇到了2个大问题. 1.http://www.oschina. ...
- Python 使用期物处理并发
抨击线程的往往是系统程序员,他们考虑的使用场景对一般的应用程序员来说,也许一生都不会遇到--应用程序员遇到的使用场景,99% 的情况下只需知道如何派生一堆独立的线程,然后用队列收集结果. 示例:网络下 ...
- js history对象 手机物理返回键
有兴趣的可以了解下history对象,不感兴趣也可以直接跳到手机物理返回键监听部分 使用场景: 场景1:项目中一个表单页面,需得填写验证码,填写验证码后提交,由于使用的form直接提交,没有使用AJA ...
- 《Java从入门到放弃》JavaSE篇:综合练习——单身狗租赁系统(数组版)
因为现在只学习了基本语法,所以在综合练习之前,先补充关于方法概念. 方法的作用:把一系列的代码放在一起,然后再取个别名.之后通过这个别名的调用,就相当于执行了这一系列的代码. 方法的语法:([]中的内 ...
- jQuery ajax error函数(交互错误信息的获取)
一般error函数返回的参数有三个: function(jqXHR jqXHR, String textStatus, String errorThrown).常见调用代码如下: $.ajax({ u ...