linux 进程与线程

进程与线程

为什么对于大多数合作性任务,多线程比多个独立的进程更优越呢?这是因为,线程共享相同的内存空间。不同的线程可以存取内存中的同一个变量。所以,程序 中的所有线程都可以读或写声明过的全局变量。如果曾用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

 

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值