进程与线程
为什么对于大多数合作性任务,多线程比多个独立的进程更优越呢?这是因为,线程共享相同的内存空间。不同的线程可以存取内存中的同一个变量。所以,程序 中的所有线程都可以读或写声明过的全局变量。如果曾用fork() 编写过重要代码,就会认识到这个工具的重要性。为什么呢?虽然fork() 允许创建多个进程,但 它还会带来以下通信问题:如何让多个进程相互通信,这里每个进程都有各自独立的内存空间。对这个问题没有一个简单的答案。虽然有许多不同种类的本地IPC (进程间通信),但它们都遇到两个重要障碍:
强加了某种形式的额外内核开销,从而降低性能。
对于大多数情形,IPC不是对于代码的“自然”扩展。通常极大地增加了程序的复杂性。
双重坏事: 开销和复杂性都非好事。如果曾经为了支持 IPC而对程序大动干戈过,那么您就会真正欣赏线程提供的简单共享内存机制。由于所有的线程都驻留在同一内存空间,POSIX线程无需进行开销大而复杂的长距离调用。只要利用简单的同步机制,程序中所有的线程都可以读取和修改已有的数据结构。而无需将数据经由文件描述符转储或挤入紧窄的共享内存空间。仅此一个原因,就足以让您考虑应该采用单进程/多线程模式而非多进程/单线程模式。
为什么要用线程?
与标准 fork()相比,线程带来的开销很小。内核无需单独复制进程的内存空间或文件描述符等等。这就节省了大量的CPU时间,使得线程创建比新进程创建快上十到一百倍。因为这一点,可以大量使用线程而无需太过于担心带来的CPU 或内存不足。使用 fork() 时导致的大量 CPU占用也不复存在。这表示只要在程序中有意义,通常就可以创建线程。
当然,和进程一样,线程将利用多CPU。如果软件是针对多处理器系统设计的,这就真的是一大特性(如果软件是开放源码,则最终可能在不少平台上运行)。
特定类型线程程序(尤其是CPU密集型程序)的性能将随系统中处理器的数目几乎线性地提高。如果正在编写CPU非常密集型的程序,则绝对想设法在代码中使用多线程。一旦掌握了线程编码,无需使用繁琐的IPC和其它复杂的通信机制,就能够以全新和创造性的方法解决编码难题。所有这些特性配合在一起使得多线程编程更有趣、快速和灵活.
什么是线程?
专业点的说法,线程被定义为一个独立的指令流,它本身的运转由操作系统来安排,但是,这意味着什么呢?再进一步,设想一个包含了大量procedure的主程序,然后想象所有这些procedure在操作系统的安排下一起或者独立的运行,这就是对于多线程程序的一个简单描述。问题是,它是如何实现的呢?
在弄懂线程之前,第一步要搞清楚Unix进程。进程被操作系统创建,并需要相当多的“开支”,进程包含如下程序资源和程序执行状态信息:
进程ID,进程群组ID,用户ID,群组ID
环境
工作目录
程序指令
寄存器
栈
堆
文件描述符
信号动作
共享库
进程间通信工具(例如消息队列,管道,信号量,共享内存)
所以,总的来说,Unix环境里的线程有如下特点:
它生存在进程中,并使用进程资源;
拥有它自己独立的控制流,前提是只要它的父进程还存在,并且OS支持它;
它仅仅复制可以使它自己调度的必要的资源;
它可能会同其它与之同等独立的线程分享进程资源;
如果父进程死掉那么它也会死掉——或者类似的事情;
它是轻量级的,因为大部分的开支已经在它的进程创建时完成了。
因为在同一进程内的线程分享资源,所以:
一个线程对共享的系统资源做出的改变(例如关闭一个文件)会被所有的其它线程看到;
指向同一地址的两个指针的数据是相同的;
对同一块内存进行读写操作是可行的,但需要程序员作明确的同步处理操作。
相关操作函数:
进程的产生方式:fork、system、exec
程序与进程的差别、进程产生过程、终止方式、进程间的通信、进程间的同步、进程与线程
进程产生方式:
1、进程号:
#include<sys/types.h>
#include<unistd.h>
pid_t getpid(void);
pid_t getppid(void);
//实例:
[root@localhost 03c]# vim pid.c
#include<sys/types.h>
#include<unistd.h>
int main()
{
pid_t pid,ppid;
pid=getpid();
ppid=getppid();
printf("current pid:%d\n",pid);
printf("current ppid:%d\n",ppid);
return 0;
}
[root@localhost 03c]# gcc -o pid pid.c
pid.c: In function ‘main’:
pid.c:8: warning: incompatible implicit declaration of built-in function ‘printf’
[root@localhost 03c]# ./pid
current pid:3689
current ppid:2465
[root@localhost 03c]# ps -ef | grep "2465"
root 2465 2459 0 15:25 pts/0 00:00:00 bash
root 3693 2465 13 21:05 pts/0 00:00:00 ps -ef
root 3694 2465 0 21:05 pts/0 00:00:00 grep 2465
2、进程复制:
fork()
一个进程,包括代码、数据和分配给进程的资源。fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。
一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。
1)在父进程中,fork返回新创建子进程的进程ID;
2)在子进程中,fork返回0;
3)如果出现错误,fork返回一个负值;
#include<sys/types.h>
#include<unistd.h>
pid_t fork(void);
//实例:
#include<stdio.h>
#include<sys/types.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
pid_t pid;
pid=fork();
if(pid==-1)
{
printf("error!\n");
return -1;
}
else if(pid==0)
{
printf("fork:%d,pid:%d,ppid:%d\n",pid,getpid(),getppid());
}
else
{
printf("fork:%d,pid:%d,ppid:%d\n",pid,getpid(),getppid());
}
return 0;
}
[root@localhost 03c]# ./fork
fork:3749,pid:3748,ppid:2465
fork:0,pid:3749,ppid:1
3、system()方式
头文件:#include <stdlib.h>
定义函数:int system(const char * command);
相关函数:fork, execve, waitpid, popen
1、如果 system()在调用/bin/sh 时失败则返回127, 其他失败原因返回-1.。
2、若参数string 为空指针(NULL), 则返回非零值.
3、如果system()调用成功则最后会返回执行shell 命令后的返回值, 但是此返回值也有可能为system()调用/bin/sh 失败所返回的127, 因此最好能再检查errno来确认执行成功.
//实例
[root@localhost 03c]# vim system.c
#include<sys/types.h>
#include<stdio.h>
#include<unistd.h>
int main()
{
int ret;
printf("pid id:%d\n",getpid());
ret=system("ls -l");
printf("return:%d\n",ret);
return 0;
}
[root@localhost 03c]# gcc -o system system.c
[root@localhost 03c]# ./system
pid id:3770
total 48
-rwxr-xr-x. 1 root root 6505 Oct 25 20:19 01
-rw-r--r--. 1 root root 297 Oct 25 20:34 01.c
-rwxr-xr-x. 1 root root 7071 Oct 25 21:20 fork
-rw-r--r--. 1 root root 395 Oct 25 21:24 fork.c
-rwxr-xr-x. 1 root root 6738 Oct 25 21:05 pid
-rw-r--r--. 1 root root 221 Oct 25 21:14 pid.c
-rwxr-xr-x. 1 root root 6748 Oct 25 21:29 system
-rw-r--r--. 1 root root 195 Oct 25 21:29 system.c
return:0
//进程执行exec()函数
所需头文件
#include <unistd.h>
函数原型
int execl(const char *path, const char *arg, ...)
int execv(const char *path, char *const argv[])
int execle(const char *path, const char *arg, ..., char *const envp[])
int execve(const char *path, char *const argv[], char *const envp[])
int execlp(const char *file, const char *arg, ...)
int execvp(const char *file, char *const argv[])
函数返回值
成功:函数不会返回
出错:返回-1,失败原因记录在error中
exec调用举例如下
char *const ps_argv[] ={"ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL};
char *const ps_envp[] ={"PATH=/bin:/usr/bin", "TERM=console", NULL};
execl("/bin/ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL);
execv("/bin/ps", ps_argv);
execle("/bin/ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL, ps_envp);
execve("/bin/ps", ps_argv, ps_envp);
execlp("ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL);
execvp("ps", ps_argv);
请注意exec函数族形参展开第一个参数是带路径的执行码(execlp、execvp函数第一个参数是无路径的,系统会根据PATH自动查找然后合成带路径的执行码),第二个是不带路径的执行码,执行码可以是二进制执行码和Shell脚本。
//实例execlp
#include <stdio.h>
#include <unistd.h>
int main()
{
if(fork()==0)
{
if(execlp("/usr/bin/env","env",NULL)<0)
{ perror("execlp error!");
return -1 ;
}
}
return 0 ;
}
[root@localhost 03c]# gcc -o execlp execlp.c
[root@localhost 03c]# ./execlp
[root@localhost 03c]# HOSTNAME=localhost.localdomain
SELINUX_ROLE_REQUESTED=
SHELL=/bin/bash
TERM=xterm
HISTSIZE=1000
SSH_CLIENT=192.168.1.100 13140 22
SELINUX_USE_CURRENT_RANGE=
QTDIR=/usr/lib64/qt-3.3
QTINC=/usr/lib64/qt-3.3/include
SSH_TTY=/dev/pts/0
USER=zko
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
//实例execve
#include<stdio.h>
#include<unistd.h>
int main()
{
char *args[]={"/bin/ls,NULL"};
printf("pid:ID,%d\n",getpid());
if(execve("/bin/ls",args,NULL)<0)
{
printf("error!\n");
}
return 0;
}
[root@localhost 03c]# gcc -o execve execve.c
[root@localhost 03c]# ./execve
pid:ID,4075
01 01.c execlp execlp.c execve execve.c fork fork.c pid pid.c system system.c
进程间的通信和同步:
Linux多进程间的通信机制叫做IPC,多进程进行相互沟通的一种方法;其他方法:半双工、FIFO(命令管道)、消息队列、信号量、共享内存
1、半双工管道
2、pipe()函数
#include<unistd.h>
int pipe(int filedes[2]);
函数说明: pipe()会建立管道,并将文件描述词由参数filedes数组返回。
filedes[0]为管道里的读取端
filedes[1]则为管道的写入端。
返回值: 若成功则返回零,否则返回-1,错误原因存于errno中。
错误代码:
EMFILE 进程已用完文件描述词最大量
ENFILE 系统已无文件描述词可用。
EFAULT 参数 filedes 数组地址不合法。
//实例:
#include <unistd.h>
#include <stdio.h>
int main( void )
{
int result=-1;
int fd[2],nbytes;
pid_t pid;
int *write_fd=&fd[1];
int *read_fd=&fd[0];
result=pipe(fd);
if (result ==-1)
{
printf( "pipe error.\n" );
return -1;
}
else
printf("pipe success!\n");
pid=fork();
if(pid == -1)
{
printf( "fork error.\n" );
return -1;
}
if(pid==0) close(*read_fd);
else close(*write_fd);
return 0;
}
[root@localhost 03c]# gcc -o pipe pipe.c
[root@localhost 03c]# ./pipe
pipe success!
//子进程写入,父进程读出
//实例:
[root@localhost 03c]# vim pipe1.c
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
int main( void )
{
int result=-1;
int fd[2],nbytes;
char name[]="hello,world!";
char readbuf[80];
pid_t pid;
int *write_fd=&fd[1];
int *read_fd=&fd[0];
result=pipe(fd);
if(result ==-1)
{
printf( "pipe error.\n" );
return -1;
}
else
printf("pipe success!\n");
pid=fork();
if(pid == -1)
{
printf( "fork error.\n" );
return -1;
}
if(pid==0)
{
close(*read_fd);
result=write(*write_fd,name,strlen(name));
return 0;
}
else
{
close(*write_fd);
nbytes=read(*read_fd,readbuf,sizeof(readbuf));
printf("receive %d datas content:%s\n",nbytes,readbuf);
}
return 0;
}
[root@localhost 03c]# gcc -o pipe1 pipe1.c
[root@localhost 03c]# ./pipe1
pipe success!
receive 12 datas content:hello,world!?
//管道操作原子性:
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#define K 1024
#define WRITELEN (128*K)
int main( void )
{
int result=-1;
int fd[2],nbytes;
char name[WRITELEN]="hello,world!";
char readbuf[10*K];
pid_t pid;
int *write_fd=&fd[1];
int *read_fd=&fd[0];
result=pipe(fd);
if(result ==-1)
{
printf( "pipe error.\n" );
return -1;
}
else
printf("pipe success!\n");
pid=fork();
if(pid == -1)
{
printf( "fork error.\n" );
return -1;
}
if(pid==0)
{
int writesize=WRITELEN;
result=0;
close(*read_fd);
while(writesize>=0)
{
result=write(*write_fd,name,writesize);
if(result>0)
{
writesize-=result;
printf("write %d datas,remain %d datas\n",result,writesize);
}
else
sleep(10);
}
return 0;
}
else
{
close(*write_fd);
while(1)
{
nbytes=read(*read_fd,readbuf,sizeof(readbuf));
if(nbytes<=0)
{
printf("no datas to write\n");
break;
}
printf("receive %d datas content:%s\n",nbytes,readbuf);
}
}
return 0;
}
[root@localhost 03c]# gcc -o pipe2 pipe2.c
[root@localhost 03c]# ./pipe2
pipe success!
receive 10240 datas content:hello,world!
receive 10240 datas content:
receive 10240 datas content:
receive 10240 datas content:
receive 10240 datas content:
receive 10240 datas content:
receive 10240 datas content:
receive 10240 datas content:
receive 10240 datas content:
receive 10240 datas content:
receive 10240 datas content:
receive 10240 datas content:
receive 8192 datas content:
write 131072 datas,remain 0 datas
//线程编程
创建线程的函数;pthread_create
#include<pthread.h>
int pthread_create(pthread_t *tidp,const pthread_attr_t *attr,(void*)(*start_rtn)(void*),void *arg);
第一个参数为指向线程标识符的指针。
第二个参数用来设置线程属性。
第三个参数是线程运行函数的起始地址。
最后一个参数是运行函数的参数。
//函数pthread_join用来等待一个线程的结束
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
描述 :
pthread_join()函数,以阻塞的方式等待thread指定的线程结束。当函数返回时,被等待线程的资源被收回。如果进程已经结束,那么该函数会立即返回。并且thread指定的线程必须是joinable的。
参数 :
thread: 线程标识符,即线程ID,标识唯一线程。
retval: 用户定义的指针,用来存储被等待线程的返回值。
返回值 : 0代表成功。 失败,返回的则是错误号
//线程通过调用pthread_exit函数终止执行;
这个函数的作用是,终止调用它的线程并返回一个指向某个对象的指针。
原型:void pthread_exit(void *retval)
用法:#include <pthread.h>
//实例:
#include<stdio.h>
#include<pthread.h>
static int run=-1;
static int retvalue;
void *start_routine(void *argc)
{
int *running=argc;
printf("子线程初始化完毕,传入参数为:%d\n",*running);
while(*running)
{
printf("子线程正在运行....\n");
usleep(1);
}
printf("子线程退出!\n");
retvalue=8;
pthread_exit((void*)&retvalue);
}
int main()
{
pthread_t pt;
int ret=-1;
int times=3;
int i=0;
int *ret_join=NULL;
ret=pthread_create(&pt,NULL,(void*)start_routine,&run);
if(ret!=0)
{
printf("建立线程失败!\n");
return 1;
}
usleep(1);
for(i=0;i<times;i++)
{
printf("主线程打印\n");
usleep(1);
}
run=0;
pthread_join(pt,(void*)&ret_join);
printf("线程返回值为:%d\n",*ret_join);
return 0;
}
[root@localhost 03c]# gcc -o pthread pthread.c
/tmp/ccAzx5ip.o: In function `main':
pthread.c:(.text+0xad): undefined reference to `pthread_create'
pthread.c:(.text+0x121): undefined reference to `pthread_join'
collect2: ld returned 1 exit status
[root@localhost 03c]# gcc -o pthread pthread.c -lpthread
//由于pthread 库不是 Linux 系统默认的库,连接时需要使用静态库 libpthread.a,所以在使用pthread_create()创建线程,在编译中要加 -lpthread参数。
[root@localhost 03c]# ./pthread
子线程初始化完毕,传入参数为:-1
子线程正在运行....
主线程打印
子线程正在运行....
主线程打印
子线程正在运行....
主线程打印
子线程退出!
线程返回值为:8