`
hulianwang2014
  • 浏览: 703652 次
文章分类
社区版块
存档分类
最新评论
  • bcworld: 排版成这样,一点看的欲望都没有了
    jfinal

linux下的简单进程创建

 
阅读更多
1. 进程是资源分配的最小单位,而线程是调度的最小单位。
2. 进程有独立的地址空间,拥有自己的代码段数据段堆栈段,而线程只有独立的堆栈段;
3. 进程拥有多种通信方式,而线程之间通信只有通过全局变量或者创建时传值。

之所以要使用多线程
1.和进程相比,它是一种非常节约的多任务操作方式。启动一个新进程,必须分配给它独立的地址空间
建立众多的数据表来维护它的代码段数据段和堆栈段,而创建一个进程中的线程,他们公用相同的地址
空间,共享大部分数据,启动一个线程所话费的时间远远小于一个进程。
2.线程间通信非常方便。不同的进程中通信,不仅费时,而且很不方便,线程则不然,由于同一个进程下的
线程共享数据空间,所以一个线程的数据可以直接为其他线程使用。
线程的创建方法,使用pthread_crpthread_t在头文件/usr/include/bits/pthreadtypes.h中定义:
  typedef unsigned long int pthread_t;
  它是一个线程的标识符。函数pthread_create用来创建一个线程,它的原型为:
  extern int pthread_create __P ((pthread_t *__thread, __const pthread_attr_t *__attr,
  void *(*__start_routine) (void *), void *__arg));
  第一个参数为指向线程标识符的指针,第二个参数用来设置线程属性,第三个参数是线程运行函数的起始地址,最后一个参数是运行函数的参数。这里,我们的函数thread不需要参数,所以最后一个参数设为空指针。第二个参数我们也设为空指针,这样将生成默认属性的线程。对线程属性的设定和修改我们将在下一节阐述。当创建线程成功时,函数返回0,若不为0则说明创建线程失败,常见的错误返回代码为EAGAIN和EINVAL。前者表示系统限制创建新的线程,例如线程数目过多了;后者表示第二个参数代表的线程属性值非法。创建线程成功后,新创建的线程则运行参数三和参数四确定的函数,原来的线
程则继续运行下一行代码。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>

struct menber
{
    int a;
    char *s;
};

/*
 * 线程执行函数
 * */
void *create(void *arg)
{
    struct menber *temp;
    temp=(struct menber *)arg;
    printf("menber->a = %d  \n",temp->a);
    printf("menber->s = %s  \n",temp->s);
    return (void *)0;
}

/*
 * 程序入口
 * */
int main(int argc,char *argv[])
{
    pthread_t tidp;
    int error;
    struct menber *b;

	/*为结构体指针b分配内存并赋值*/
    b=(struct menber *)malloc( sizeof(struct menber) );
    b->a = 4;
    b->s = "zieckey";
    
	/*创建线程并运行线程执行函数*/
    error = pthread_create(&tidp, NULL, create, (void *)b);
    if( error )
    {
        printf("phread is not created...\n");
        return -1;
    }

    sleep(1); //进程睡眠一秒使线程执行完后进程才会结束

    printf("pthread is created...\n");
    return 0;
}


由运行结果可以看到,线程打印出了在主函数中赋值的结构体,将这个结构体的值传入了线程中使用。

Linux中,默认情况下是在一个线程被创建后,必须使用此函数对创建的线程进行资源回收,但是可以设置Threads attributes来设置当一个线程结束时,直接回收此线程所占用的系统资源,详细资料查看Threads attributes。
其实在Linux中,新建的线程并不是在原先的进程中,而是系统通过一个系统调用clone()。该系统copy了一个和原先进程完全一样的进程,并在这个进程中执行线程函数。不过这个copy过程和fork不一样。 copy后的进程和原先的进程共享了所有的变量,运行环境。这样,原先进程中的变量变动在copy后的进程中便能体现出来。

pthread_join的应用

pthread_join使一个线程等待另一个线程结束。
代码中如果没有pthread_join主线程会很快结束从而使整个进程结束,从而使创建的线程没有机会开始执行就结束了。加入pthread_join后,主线程会一直等待直到等待的线程结束自己才结束,使创建的线程有机会执行。
所有线程都有一个线程号,也就是Thread ID。其类型为pthread_t。通过调用pthread_self()函数可以获得自身的线程号。
测试代码:
#include <unistd.h>
#include <stdio.h>
#include <pthread.h>

/*
* 线程的执行函数
* */
void *thread(void *str)
{
    int i;
    for (i = 0; i < 3; ++i)
    {
        sleep(2);
        printf( "This in the thread : %d\n" , i );
    }
    return NULL;
}


/*
* 程序入口
* */
int main()
{
    pthread_t pth;
    int i;

	/*创建线程并执行线程执行函数*/
    int ret = pthread_create(&pth, NULL, thread, NULL);  
	printf("The main process will be to run,but will be blocked soon\n");	
	/*阻塞等待线程退出*/
    pthread_join(pth, NULL);

    printf("thread was exit\n");
    for (i = 0; i < 3; ++i)
    {
        sleep(1);
        printf( "This in the main : %d\n" , i );
    }
    return 0;
}



可以看到,调用pthread_join后进程阻塞直到线程退出。
pthread_join注释掉后可以看到结果如下:
此时进程和线程同时运行。

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <pthread.h>
#include <time.h>
pthread_t tid; sigset_t set;
void myfunc()
{
 printf("hello\n");
}
static void* mythread(void *p)
{
 int signum;
 while(1) {
	 
    sigwait(&set,&signum);
   if(SIGUSR1 == signum)
   myfunc();
   if(SIGUSR2 == signum)
     {
      printf("I will sleep 2 second and exit\n");
      sleep(2);
      break;
     }
    }
}
int main()
{
  char tmp;
  void* status;
  sigemptyset(&set);
  sigaddset(&set,SIGUSR1);
  sigaddset(&set,SIGUSR2);
  sigprocmask(SIG_SETMASK,&set,NULL);
  pthread_create(&tid,NULL,mythread,NULL);
  while(1)
  {
    printf(":");
    scanf("%c",&tmp);
    if('a' == tmp)
    {
       pthread_kill(tid,SIGUSR1);
    }
    else if('q'==tmp)
    {
     
      pthread_kill(tid,SIGUSR2);
   
      pthread_join(tid,&status); 
      printf("finish\n");
      break;
    }
    else
      continue;
  }
  return 0;
}

运行结果:
// 如果输入a,子线程打印"hello",主程序继续等待输入;
// 如果输入q,主程序等待子程序结束。子线程打印"I will sleep 2 second and exit",并延时两秒后结束。主线程随之打印"finish",程序结束。

不论是可预见的线程终止还是异常终止,都会存在资源释放的问题,在不考虑因运行出错而退出的前提下,如何保证线程终止时能顺利的释放掉自己所占用的资源,特别是锁资源,就是一个必须考虑解决的问题。

最经常出现的情形是资源独占锁的使用:线程为了访问临界资源而为其加上锁,但在访问过程中被外界取消,如果线程处于响应取消状态,且采用异步方式响应,或者在打开独占锁以前的运行路径上存在取消点,则该临界资源将永远处于锁定状态得不到释放。外界取消操作是不可预见的,因此的确需要一个机制来简化用于资源释放的编程。

在POSIX线程API中提供了一个pthread_cleanup_push()/pthread_cleanup_pop()函数对用于自动释放资源--从pthread_cleanup_push()的调用点到pthread_cleanup_pop()之间的程序段中的终止动作(包括调用pthread_exit()和取消点终止)都将执行pthread_cleanup_push()所指定的清理函数。API定义如下:

void pthread_cleanup_push(void (*routine)(void*), void *arg);

void pthread_cleanup_pop(int execute);//这里的int参数,0是不执行push的内容,非0是执行。
需要注意的问题有几点:
1,push与pop一定是成对出现的,其实push中包含"{"而pop中包含"}",少一个不行。
2,push可以有多个,同样的pop也要对应的数量,遵循"先进后出原则"。

push进去的函数可能在以下三个时机执行:
1,显示的调用pthread_exit();

2,在cancel点线程被cancel。

3,pthread_cleanup_pop()的参数不为0时。

以上动作都限定在push/pop涵盖的代码内。
测试代码:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

/*
 * 线程清理函数
 * */
void *clean(void *arg)
{
    printf("cleanup :%s\n",(char *)arg);
    return (void *)0;
}

/*
 * 线程1的执行函数
 * */
void *thr_fn1(void *arg)
{
    printf("thread 1 start  \n");
	/*将线程清理函数压入清除栈两次*/
    pthread_cleanup_push( (void*)clean,"thread 1 first handler");
    pthread_cleanup_push( (void*)clean,"thread 1 second hadler");
    printf("thread 1 push complete  \n");

    if(arg)
    {
        return((void *)1); //线程运行到这里会结束,后面的代码不会被运行。由于是用return退出,所以不会执行线程清理函数。
    }
    pthread_cleanup_pop(0);
    pthread_cleanup_pop(0);
    return (void *)1;
}

/*
 * 线程2的执行函数
 * */
void *thr_fn2(void *arg)
{
    printf("thread 2 start  \n");

	/*将线程清理函数压入清除栈两次*/
    pthread_cleanup_push( (void*)clean,"thread 2 first handler");
    pthread_cleanup_push( (void*)clean,"thread 2 second handler");
    printf("thread 2 push complete  \n");
    
	if(arg)
    {
        pthread_exit((void *)2);//线程运行到这里会结束,后面的代码不会被运行。由于是用pthread_exit退出,所以会执行线程清理函数。执行的顺序是先压进栈的后执行,即后进先出。
    }
    pthread_cleanup_pop(0);
    pthread_cleanup_pop(0);
    pthread_exit((void *)2);
}

/*
 * 程序入口
 * */
int main(void)
{
    int err;
    pthread_t tid1,tid2;
    void *tret;

	/*创建线程1并执行线程执行函数*/
    err=pthread_create(&tid1,NULL,thr_fn1,(void *)1);
    if(err!=0)
    {
        printf("error .... \n");
        return -1;
    }
	/*创建线程2并执行线程执行函数*/
    err=pthread_create(&tid2,NULL,thr_fn2,(void *)1);
	if(err!=0)
    {
        printf("error .... \n");
        return -1;
    }

	/*阻塞等待线程1退出,并获取线程1的返回值*/
    err=pthread_join(tid1,&tret);
    if(err!=0)
    {
        printf("error .... \n");
        return -1;
    }
    printf("thread 1 exit code %d  \n",(int)tret);
	/*阻塞等待线程2退出,并获取线程2的返回值*/
    err=pthread_join(tid2,&tret);
    if(err!=0)
    {
        printf("error .... ");
        return -1;
    }
	printf("thread 2 exit code %d  \n",(int)tret);  
    return 1;
}





分享到:
评论

相关推荐

    操作系统实验报告-Linux进程创建与通信.doc

    Linux 进程创建与通信 Linux 进程创建是操作系统中一个非常重要的概念,它指的是操作系统中创建新的进程,以便更好地管理和执行任务。在 Linux 中,进程创建主要通过 fork() 系统调用来实现的。fork() 系统调用创建...

    简单理解linux下进程

    创建子进程才能多道程序并发执行,linux初始化的时候会创建swap进程、然后是init进程和一个init进程的兄弟进程,所有的进程(运行的程序)都是从父进程演化出去的,你可以看看proc里的东西,写个程序打印出各个进程...

    进程的创建控制实验

    目的:创建进程,体会进程间的并发特征 内容:编写一段程序,使用系统调用fork()创建两个子进程p1和p2。而且父进程输出字符串“father”,第一个子进程输出字符串“borther1”,第二个子进程输出字符串“borther2” ...

    Linux 进程控制与进程互斥(附源代码)(附实验报告)

    1、利用fork函数创建新进程,并根据fork函数的返回值,判断自己是处于父进程还是子进程中; 2、在新创建的子进程中,使用exec类的函数启动另一程序的执行;...5、分析Linux系统下多进程与多线程中的区别。

    进程控制(Linux)操作系统实验

    设计并实现 Unix 的 “time” 命令。 “mytime” 命令通过命令行参数接受要...在 Linux 下实现 : •使用 fork()/execv() 来创建进程运行程序 •使用 wait() 等待新创建的进程结束 •调用 gettimeofday() 来获取时间

    Linux进程调度程序剖析.pdf

    Linux 采用简单的基于优先级策略来完成对进程的调度工作。由于 Linux 采用了底半处理策略,因此进程调度需要考虑中断程序和任务队列的处理,从而形成了 Linux 独具特色的调度风格。 Linux 进程调度策略可以分为两种...

    实验二 进程创建及进程间通信1

    1.进程创建程序示例: 2.进程的创建 3.运行以下程序,并分析switch语句中各个case所做的事和产生原因 4.分析以下程序的输出结果 5.编写程序:实现

    LINUX系统下多进程的创建与通信

    用C语言写一个简单的Linux终端软件,接收用户发出的类似于Windows命令行中的命令,转换成对应的Linux命令加以执行,并将执行的结果回显给用户。比如,用户输入“dir”,程序实际返回“ls”的内容。 (2)软件包含前...

    Linux内核进程调度与控制的实现.pdf

    Linux 使用了比较简单的基于优先级的进程调度算法选择新的进程。进程调度控制着进程对 CPU 的访问,当需要选择下一个进程运行时,由调度程序选择最值得运行的进程。可运行进程实际是仅等待 CPU 资源的进程,如果...

    Linux操作系统实验报告

    实验一 熟悉Linux常用命令 实验二 Linux下程序设计基础 实验三 Linux下进程间管道通信 实验四 IPC进程间共享内存通信 实验五 IPC信号量使用 实验六 Linux内存基本原理 实验八 设备驱动程序 实验九 Linux下socket网络...

    嵌入式操作系统:第8章 ARM Linux进程与进程调度.ppt

    "嵌入式操作系统:ARM Linux进程与进程调度" 本章教学目的及要求: * 理解 Linux 的进程管理 * 了解 ARM Linux 进程控制相关 API * 了解 Linux 守护进程进程相关的基本概念 嵌入式操作系统可以分为两种类型:单道...

    嵌入式Linux高级编程--04posix_进程间通信.ppt

    Linux进程间通信发展历程包括早期UNIX进程间通信、基于System V进程间通信、基于Socket进程间通信和POSIX进程间通信。 目前Linux使用的进程间通信方式包括管道、消息队列、共享内存、信号量和信号等。 管道通信是...

    linux下为程序创建启动和关闭的的sh文件,scrapyd为例.docx

    Linux 下使用 Shell Script 实现程序的启动和关闭 在 Linux 操作系统中,许多程序都需要手动启动和关闭,而这需要输入长长的命令来实现。为了简化这个过程,我们可以使用 Shell Script 来实现程序的启动和关闭。...

    在LINUX C中以多进程方式实现微型SHELL.pdf

    Linux C为用户提供了一个强大的编程环境,本文分析了微型SHELL的基本功能及实现机制,并使用Linux下的多进程编程技术设计了一个微型SHELL,最后给出了在Linux C下的实现代码。 微型SHELL的基本功能包括命令解释、...

    操作系统实验一Linux基本环境及进程管理.docx

    Linux 操作系统基本环境及进程管理实验 操作系统实验一 Linux 基本环境及进程管理实验旨在让学生熟悉 Linux 操作系统的基本环境和进程管理机制。...同时,学生将了解进程创建和进程通信的基本原理。

    linux 创建守护进程的相关知识

    创建子进程,父进程退出 这是编写守护进程的第一步。... 在子进程中创建新会话 这个步骤是创建守护进程中最重要的一步,虽然它的实现非常简单,但它的意义却非常重大。在这里使用的是系统函数setsid,在具体

    进程的创建、执行和终止实验操作系统实验.pdf

    然而,最后程序却运行了四次,这是因为每个 fork() 函数创建了一个新的子进程,而不是简单地创建了两个子进程。在第一个 fork() 被调用时,创建了一个子进程,这时程序有两个进程。由于父进程和子进程共享代码段,新...

    操作系统课程设计—进程控制

    1. 创建进程 CreateProcess() 调用的核心参数是可执行文件运行时的文件名及其命令行。下表详细地列出了每个参数的类型和名称。 CreateProcess() 函数的参数 2. 正在运行的进程 如果一个进程拥有至少一个执行...

    操作系统课程实验.rar

    由父进程创建一个管道,然后再创建 3 个子进程,并由这三个子进程利用管道与父进程 之间进行通信:子进程发送信息,父进程等三个子进程全部发完消息后再接收信息。通信的 具体内容可根据自己的需要随意设计,要求能...

    操作系统实验报告(包括线程,进程,文件系统管理,linux+shell简单命令).pdf

    操作系统实验报告包括进程、线程、文件系统管理和Linux shell基本命令的相关知识点。 一、进程管理 * 创建进程:使用CreateProcess函数创建子进程,并使用STARTUPINFO结构体来指定进程的标准输入、输出和错误句柄...

Global site tag (gtag.js) - Google Analytics