版权声明:本文为博主原创文章,未经博主允许不得转载。 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