C++多线程linux编程

Linux 多进程、多线程 C 语言实现

2019-03-17  本文已影响81人  去旅行_2016

概述

Linux 是多任务操作系统,可以同时运行多个进程,来完成多项工作。

进程就是处于活动状态的程序,占用一定的内存空间。进程可以把自己复制一份,从而创造出一个新的进程。新的进程称为 子进程,原来的进程称为 父进程

进程可以复制自己。这意味着启动一个程序,可能会产生多个进程。这样的程序能同时进行多项工作。多进程编程就是要设计一个这样的程序。

进程的调度

实际上 CPU 只能同时处理一个进程的工作。也就是说,进程并不是真的同时都在运行。

Linux 是一个 分时 操作系统。在同一时刻,只有一个进程得到 CPU 的处理,但很快就会变成另一个进程,如此往复。虽然每个进程一次只占用 CPU 很短的一段时间,但是每个进程总是很快就能再一次占用 CPU,所以这些进程看起来就好像一直都在运行一样。

CPU 在工作时就好像人体的循环系统一样。心脏的跳动维持血液的流动;晶振的振荡维持 CPU 的运行。心脏一旦停止跳动,血液也就停止流动;晶振一旦停止振荡,CPU 也就停止工作。

晶振在单位时间内的振荡次数称为 时钟频率,两次振荡之间的时间间隔称为 时钟周期。进程占用 CPU 的时间应该以时钟周期为单位来计量。

进程的状态

进程从创建到运行结束,经历的全部过程,称为进程的生命周期。在生命周期的不同阶段,进程会呈现不同的状态。下表列出了进程可能出现的所有状态。

状态 含义
创建状态 正在被创建
就绪 刚刚创建好,还没运行过
内核状态 运行中
用户状态 暂停中
睡眠 已经轮到这个进程上场了,但是它的某些需求得不到满足,只能继续等待
唤醒 正在睡眠的进程,正在被唤醒
被抢占 运行到一半,CPU 被另一个进程抢占
僵死状态 进程已经结束,但记录还在

进程的控制

使用下文讨论的几个函数需要包含下面几个头文件。

#include <stdlib.h> // exit()
#include <unistd.h> // fork(), sleep(), _exit(), pid_t
#include <sys/wait.h> // wait()

fork() 创建子进程

子进程通过 fork() 函数创建。fork() 不需要任何参数,返回值是 pid_t 型。pid_t 型实际上就是 int 型。这是专门用来保存进程 PID(进程的编号)的类型。

pid_t fork(void);

如果子进程创建成功,fork() 函数将返回子进程的 PID,否则返回 -1

前面说过,创建子进程相当于把自己复制一份。也就是说,创建出来的子进程和父进程几乎是一模一样的,并且都将接着执行 fork() 函数后面的代码。

不同的是,对于 fork() 函数的返回值,在子进程中将得到 0。因此,如果在 fork() 函数之后用一个 if 语句对 fork() 函数的返回值进行判断,子进程和父进程将进入不同的分支。

pid_t cpid;

cpid = fork();
if (cpid == -1) {
    printf("Create process failed!\n");
    exit(1);
}

if (cpid == 0) {
    printf("Hello from Child!\n");
} else {
    printf("Hello from Parent!\n");
}

sleep() 主动睡眠

进程调用 sleep() 函数,将进入睡眠状态,传递给 sleep() 函数的参数就是睡眠的持续时间,单位秒。下面代码将使进程进入睡眠状态,持续 3 秒钟。

sleep(3);

wait() 等待进程结束、exit() 进程结束

父进程调用 wait(),将进入睡眠状态,以等待子进程进入僵死状态。子进程调用 exit() 将使自己进入僵死状态。

pid_t wait(int * status);
void exit(int status);

这两个函数都只有一个参数。exit() 函数的参数是一个整型变量,用来保存一个范围在 0-255 之间的整数。wait() 函数的参数要求一个整型变量的地址,这个整型变量将保存这个整数。

多线程

进程进一步细分,就是线程。每一个进程都至少有一个线程,这个线程称为 主线程。主线程就是运行主函数 main() 的线程。创建线程相当于调用一个函数,只不过原来的线程会立即执行后续的代码而不等待这个函数返回。这使得被调函数中的代码和后续的代码是并行执行的。因此,可以简单地认为多线程就是同时运行多个函数。

历史上曾出现过多种线程标准。这些标准互不兼容,这使得程序员难以开发可移植的应用程序。为此,IEEE 制订了后来被广泛采用的线程标准 POSIX threads,简称 Pthreads。POSIX 线程库 实现了这个标准。POSIX 线程库也是最常用的线程库。使用 POSIX 线程库需要包含头文件 pthread.h

#include <pthread.h>

由于 POSIX 线程库并不属于默认库,因此在使用 gcc 命令进行编译时,要加上 -lpthread 选项。

pthread_create() 创建线程

线程通过调用 pthread_create() 函数创建。

int pthread_create(
    pthread_t * id, 
    pthread_attr_t * attr, 
    void * (* start_routine)(void *), 
    void * arg
);

如果线程创建成功,pthread_create() 函数将返回 0,否则返回要给错误代码。这些错误代码是线程库定义的一些常量,但没有一个是 -1

pthread_exit() 线程结束

线程调用 pthread_exit() 函数可结束自己,这个函数相当于结束进程的 exit()

void pthread_exit(void * retval);

唯一的参数是一个 void 指针,用来指向返回值。

pthread_join() 等待线程结束

可以调用 pthread_join() 函数来等待另一个线程结束。

int pthread_join(pthread_t id, void ** retval);

如果顺利,pthread_join() 函数将返回 0,否则返回一个错误代码。

下面是一个完整的例子。演示了从创建线程到结束线程的过程。

#include <stdio.h>
#include <stdlib.h> // exit()
#include <unistd.h> // fork(), sleep(), _exit(), pid_t
#include <pthread.h>

void * hello(void * arg)
{
    printf("Thread start running!\n");
    printf("%s\n", (char *)arg);
    sleep(3);
    pthread_exit("Hello from thread!");
}

int main(void)
{
    pthread_t id;
    void * thread_retval;

    if (pthread_create(&id, 0, hello, "Hello from main!") != 0) {
        printf("Create thread failed!\n");
        exit(1);
    }

    pthread_join(id, &thread_retval);
    printf("%s\n", (char *)thread_retval);
    return 0;
}

pthread_detach() 脱离同步

pthread_detach() 函数用来使一个线程与其他线程脱离同步。脱离同步是指其他线程不能用 pthread_join() 函数来等待这个线程结束。这个线程将在退出时自行释放所占的资源。

int pthread_detach(pthread_t id);

pthread_detach() 函数唯一的参数就是需要脱离同步的线程的标识符。如果顺利,将返回 0,否则返回一个错误代码。

上一篇 下一篇

猜你喜欢

热点阅读