POSIX多线程—异步编程举例

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/livelylittlefish/article/details/7952884

作者:阿波
链接:http://blog.csdn.net/livelylittlefish/article/details/7952884

(整半年没有更新,发几篇以前的读书笔记。)

Content

0.

1. 基本的同步版本

2. 多进程版本

3. 多线程版本

4. 小结

 

 

0.

 

本节通过一个简单的闹钟实例演示异步编程方法。

该程序循环接受用户输入信息,直到出错或者输入完毕。用户输入的每行信息有两部分:闹钟等待的时间()和闹钟时间到达时显示的文本信息。

 

1. 基本的同步版本

代码如下。

/*
 * alarm.c
 *
 * Simple synchronous alarm program. This is used as a
 * reference for progressive examples of asynchronous
 * alarm programs.
 */
#include "errors.h"

int main (int argc, char **argv)
{
    int seconds;
    char line[128];
    char message[64];

    while (1)
    {
        printf("Alarm> ");
        if (fgets(line, sizeof(line), stdin) == NULL)
            exit(0);
        if (strlen(line) <= 1)
            continue;

        /**
         Parse input line into seconds (%d) and a message (%64[^\n]), 
         consisting of up to 64 characters separated from the seconds
         by whitespace.
        */
        if (sscanf(line, "%d %64[^\n]", &seconds, message) < 2)
        {
            fprintf(stderr, "Bad command\n");
        }
        else
        {
            sleep(seconds);
            printf("(%d) %s\n", seconds, message);
        }
    }
}

特点:同步实现异步,闹钟请求后,程序睡眠等待闹钟时间到;

问题:用同步方式来实现异步,致一次只能处理一个闹钟请求;即程序睡眠时间到后才能进行下一次闹钟请求;

运行结果。

# ./alarm
Alarm> 5
Bad command
Alarm> 5 this is a test with 5 seconds
(5) this is a test with 5 seconds      //等待5秒并打印该消息
Alarm> 10 the seconds with 10 seconds
(10) the seconds with 10 seconds       //等待10秒并打印该消息
Alarm> 

2. 多进程版本

代码如下。

/*
 * alarm_fork.c
 *
 * This version of alarm.c uses fork to create a new process to
 * wait for each alarm to expire.
 */
#include <sys/types.h>
#include <wait.h>
#include "errors.h"

int main(int argc, char **argv)
{
    int status;
    char line[128];
    int seconds;
    pid_t pid;
    char message[64];

    while (1)
    {
        printf ("Alarm> ");
        if (fgets(line, sizeof(line), stdin) == NULL) exit(0);
        if (strlen(line) <= 1) continue;

        /**
         Parse input line into seconds (%d) and a message (%64[^\n]), 
         consisting of up to 64 characters separated from the seconds
         by whitespace.
        */
        if (sscanf(line, "%d %64[^\n]", &seconds, message) < 2)
        {
            fprintf(stderr, "Bad command\n");
        }
        else
        {
            pid = fork();
            if (pid == (pid_t)-1)
                errno_abort("Fork");
            if (pid == (pid_t)0)
            {
                /*
                 * If we're in the child, wait and then print a message
                 */
                sleep(seconds);
                printf("pid = %d, (%d) %s\n", pid, seconds, message);
                exit(0);
            }
            else
            {
                /*
                 * In the parent, call waitpid() to collect any children that
                 * have already terminated.
                 */
                do
                {
                    printf("line = %d, pid = %d\n", __LINE__, pid);
                    pid = waitpid((pid_t)-1, NULL, WNOHANG);
                    printf("line = %d, pid = %d\n", __LINE__, pid);
                    if (pid == (pid_t)-1)
                        errno_abort("Wait for child");
                } while (pid != (pid_t)0);
            }
         }
    }
}

特点:在子进程中异步地调用sleep函数,而父进程继续运行;

问题:对每次闹钟请求,使用fork创建子进程,程序开销过大;

waitpid()函数

  • WNOHANG:父进程不必挂起等待子进程的结束;
  • 如果有子进程终止,该函数回收该子进程的资源;
  • 如果没有子进程终止,该函数立即返回pid=0;父进程继续回收终止的子进程直到没有子进程终止;
  • 在每个命令之后循环调用waitpid,来回收所有结束的子进程;

运行结果。

Alarm> 3 test 1                        //输入命令
Alarm> 5 test 2                        //输入命令
Alarm> (3) test 1                      //等待3秒并打印该消息
10 test 3                              //输入命令
Alarm> (5) test 2                      //等待5秒并打印该消息

Alarm> (10) test 3                     //等待10秒并打印该消息

Alarm> 


# ./alarm_fork
Alarm> 5 test 1
line = 56, pid = 7901
line = 58, pid = 0
Alarm> 10 test 2
line = 56, pid = 7902
line = 58, pid = 0
Alarm> pid = 0, (5) test 1
pid = 0, (10) test 2

Alarm> 15 test 3
line = 56, pid = 7904
line = 58, pid = 7901
line = 56, pid = 7901
line = 58, pid = 7902
line = 56, pid = 7902
line = 58, pid = 0
Alarm> pid = 0, (15) test 3

Alarm> 

3. 多线程版本

代码如下。

/*
 * alarm_fork.c
 *
 * This version of alarm.c uses pthread_create to create a
 * separate thread to wait for each alarm to expire.
 */
#include <pthread.h>
#include "errors.h"

typedef struct alarm_tag {
    int         seconds;
    char       message[64];
} alarm_t;

void *alarm_thread (void *arg)
{
    alarm_t *alarm = (alarm_t*)arg;
    int status;

    status = pthread_detach (pthread_self ());
    if (status != 0)
        err_abort (status, "Detach thread");
    sleep (alarm->seconds);
    printf ("(%d) %s\n", alarm->seconds, alarm->message);
    free (alarm);
    return NULL;
}

int main (int argc, char *argv[])
{
    int status;
    char line[128];
    alarm_t *alarm;
    pthread_t thread;

    while (1) {
        printf ("Alarm> ");
        if (fgets (line, sizeof (line), stdin) == NULL) exit (0);
        if (strlen (line) <= 1) continue;
        alarm = (alarm_t*)malloc (sizeof (alarm_t));
        if (alarm == NULL)
            errno_abort ("Allocate alarm");

        /*
         * Parse input line into seconds (%d) and a message
         * (%64[^\n]), consisting of up to 64 characters
         * separated from the seconds by whitespace.
         */
        if (sscanf (line, "%d %64[^\n]", &alarm->seconds, alarm->message) < 2) {
            fprintf (stderr, "Bad command\n");
            free (alarm);
        } else {
            status = pthread_create(&thread, NULL, alarm_thread, alarm);
            if (status != 0)
                err_abort (status, "Create alarm thread");
        }
    }
}

特点:在该例子中,alarm线程调用pthread_detach()函数来分离自己,通知pthreads不必关心它的终止时间和退出状态;因此不需要等待线程结束,除非希望获得它的返回值。alarm线程的资源在它终止后立即回收。

一般地,pthreads会保存线程的资源以使其他线程了解它已经终止并获得其最终结果。在本例中,alarm线程负责自己分离自己。

该版本用到以下函数:

  • pthread_create():创建线程,运行代码由第三个参数指定,运行代码需要的参数由第四个参数传入;返回线程标志符;
  • Pthread_detach():当线程终止时允许pthreads立即回收线程资源;
  • pthread_exit():终止调用线程;

运行结果。

# ./alarm_thread
Alarm> 5 atest
Alarm> 10 btest
Alarm> (5) atest

Alarm> 15 ctest
Alarm> (10) btest
(15) ctest

Alarm> 

4. 小结

多进程版本

  • 每个闹钟有一个从主进程拷贝的独立地址空间;
  • 主进程调用waitpid来告诉系统释放其创建的子进程资源;

多线程版本

  • 所有线程共享同一个地址空间;
  • 不需要等待线程结束,除非希望获得它的返回值;
  • 线程间传递消息更快
    • 不需要映射共享内存;
    • 不需要通过管道读写;
    • 不需要担心进程间传送的地址指针是否一致;
  • 线程间共享一切:一个线程内有效的地址指针在所有线程内同样有效;

多线程编程优点

  • 在多处理器系统中开发程序的并行性(仅并行性需要特殊硬件支持)
  • 在等待慢速外设I/O操作结束的同时,程序可以执行其他计算,为程序的并发提供更有效、更自然的开发方式;
  • 一种模块化编程模型,能清晰地表达程序中独立事件间的相互关系;

多线程编程缺点

  • 计算负荷,如线程间同步需要的时间代价;
  • 需要设立较好的编程规则,如要避免死锁、竞争和优先级倒置等;
  • 更难以调试;

何种应用适合使用多线程?

  • 计算密集型应用,为了能在多处理器系统上运行,将这些计算分解到多个线程中实现;
  • IO密集型应用,为提高性能,将IO操作重叠;如分布式服务器;

 

Reference

# man waitpid

# man pthread_create

# man pthread_detach

猜你喜欢

转载自blog.csdn.net/livelylittlefish/article/details/7952884