IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C中进程与线程的基本使用(结合代码讲解) -> 正文阅读

[C++知识库]C中进程与线程的基本使用(结合代码讲解)

目录

零、前言

一、进程

?1、基本介绍与基本使用?

2、僵尸进程的产生与解决

(1)原因

(2)解决办法

?二、线程

1、进程的不足

2、线程的基本介绍

3、线程的五个基本操作

(1)创建线程

(2)关闭线程

(3)获取自身线程的 ID?

(4)阻塞等待线程的退出并释放资源

(5)子线程脱离

(6)example(pthread_join主线程阻塞等待后释放资源)

(7)example(pthread_detach子线程结束后自动释放资源)

4、多线程的数据共享

(1)互斥锁的基本使用

(2)example(注意两种初始化互斥锁的方法)


零、前言

? ? ? ? 一个简单的总结,内容也比较简单,不是很完整,其他的待日后补充,比如进程间的数据通讯方式、多线程的其他锁的实现和条件变量等等。文章中可能出现内容有异议的,欢迎评论区讨论。下面的内容每部分都结合了demo进行介绍,希望你能从中受益。文章中的内容主要来源于 《unix网络编程卷一》。

一、进程

?1、基本介绍与基本使用?

????????使用fork函数创建了一个新的进程,新进程(子进程)与原有的进程(父进程)一模一样。子进程和父进程使用相同的代码段;子进程拷贝了父进程的堆栈段和数据段。子进程一旦开始运行,它复制了父进程的一切数据,各自运行,互不影响。而且由于子进程复制了父进程的堆栈段,所以子进程也复制了父进程的代码执行进度。

#include <unistd.h>
pid_t fork(void);

?????????fork 函数返回两个值,对于调用进程(父进程)返回新派生进程的进程 ID 号;对于子进程返回 0。使用 getpid 函数可以查看当前进程的 ID 号。

????????子进程可以通过 getppid 函数来获取父进程的进程 ID 号;相反创建子进程后父进程无法再获得子进程的 ID号,唯一的方法就是在调用 fork 函数的时候保存返回值。父进程调用 fork 函数之前打开的所有描述符在 fork 函数返回后由子进程分享。

????????进程间相互独立,互不影响。每个进程都有自己的地址空间,所以可能会出现,不同进程的变量虚拟地址相同的现象,其实他们指向的物理地址不同。

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
 
int ii=10;
int main()
{
  int jj=20;
  if (fork()>0)
  {
    ii=11;jj=21; sleep(1);  printf("父进程:ii=%d,jj=%d\n",ii,jj);
  }
  else
  {
    ii=12;jj=22; sleep(1);  printf("子进程:ii=%d,jj=%d\n",ii,jj);
  }
  /*
      ffy@ffy:~/桌面/work/code$ ./a.out 
      父进程:ii=11,jj=21
      子进程:ii=12,jj=22
  */
}

2、僵尸进程的产生与解决

(1)原因

? ? ? ? 在linux系统中可以通过 top 这个工具查看僵尸进程,如下图右上角的 zombie 即为僵尸进程的数量。

????????a. 在子进程退出前父进程先退出,则系统会让init进程接管子进程;

????????b. 当子进程先于父进程终止,而父进程又没有调用wait函数等待子进程结束,子进程进入僵尸状态,并且会一直保持下去除非主进程退出。子进程处于僵尸状态时,内核只保存该进程的一些必要信息以备父进程所需。此时子进程始终占用着资源,同时也减少了系统可以创建的最大进程数;

????????c. 如果子进程先于父进程终止,且父进程调用了 wait 或 waitpid 函数,则父进程会等待子进程结束(阻塞等待)。

(2)解决办法

????????a.? 在Linux下,可以简单地将 SIGCHLD 信号的操作设为 SIG_IGN,这样当子进程结束时就不会称为僵尸进程。

signal(SIGCHLD,SIG_IGN);  // 忽略子进程退出的信号,避免产生僵尸进程
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc, char const *argv[])
{
    pid_t pid = fork();
    signal(SIGCHLD,SIG_IGN);
    if (pid == 0) {
        sleep(1);
    } else {
        sleep(20)
    }
    return 0;
}

????????b. 主进程可以通过调用 pid_t wait(int *statloc) 来阻塞的等待子进程的退出。wait 函数将子进程终止时传递的参数值保存到 statloc 所指向的内存空间。然后通过宏 WIFEXITED(status) 确定子进程是否正常终止,通过宏 WEXITSTATUS(status) 获得子进程的返回值。

pid_t wait(int *statloc)
// statloc = &status
WIFEXITED(status)
WEXITSTATUS(status)
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc, char const *argv[])
{
    pid_t pid = fork();
    if (pid == 0) {
        sleep(15);
        return 3;
    } else {
        int status;
        pid = wait(&status);
        printf("child process exited\n");
        if (WIFEXITED(status)) {
            printf("child process return : %d\n", WEXITSTATUS(status));
        }
    }
    return 0;
}

????????c.? 使用 waitpid,例如:waitpid(-1, &status, WNOHANG)?; -1 表示可以等待任意子进程终止,WNOHANG 表示子进程没有终止也不会进入阻塞状态,而是返回0并退出函数。

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc, char const *argv[])
{
    pid_t pid = fork();
    if (pid == 0) {
        sleep(15);
        return 3;
    } else {
        int status;
        while (!waitpid(-1, &status, WNOHANG)) {
            printf("child process in running\n");
            printf("do someting\n");
        }
        printf("child process exited\n");
        if (WIFEXITED(status)) {
            printf("child process return : %d\n", WEXITSTATUS(status));
        }
    }
    return 0;
}

?二、线程

1、进程的不足

????????(1)、不断的生成子进程占用大量资源,因为每个子进程都要把父进程的内存映像复制到子进程中去,并在子进程中复制所有的描述符等。也可以说子进程和父进程完全一样,连程序的执行进度都完全一样,拥有独立的内存空间。

????????(2)、子进程生成后会复制父进程的所有数据,包括调用fork之前的数据。所以父进程向尚未创建的子进程传递信息很容易,但子进程向父进程传递返回消息十分的吃力,需要借助进程间通讯机制。

2、线程的基本介绍

????????线程是轻量级的进程,创建速度更快。同一个进程内的线程共享相同的全局内存,这使得线程间共享信息十分方便,但要面临同步的问题。

????????下面是线程共享与独有的资源。

共享

独有

进程指令

线程ID

描述符

信号与信号处理函数

寄存器集合(程序计数器和栈)

用户ID与组ID

errno

大多数数据

信号掩码

优先级

3、线程的五个基本操作

(1)创建线程

#include <pthread.h>
int pthread_create(pthread_t *tid, const pthread_attr_t *attr,
                    void *(*func)(void *), void *arg);

????????程序正常启动后会自动创建主线程,其余线程才由 pthread_create 创建。每个线程由线程ID(thread ID)标识,其数据类型是 pthread_t(unsigned int)。

????????每个线程都有很多属性:优先级、初始栈大小、是否是守护进程等,可以通过 attr 这个参数传递,默认情况下传递空指针。

????????最后指导线程的执行函数 func 和该函数的唯一调用参数 arg,如果需要传递多个参数可以打包成结构体传值。func 传入参数和返回值都是通用指针(void *)。

????????pthread_create 成功创建线程返回 0,不成功返回 非0(正数)。

(2)关闭线程

????????线程退出的两种方式,一种是子线程执行函数的退出(隐式、显示);另一种是主线程的退出(程序进程结束,其附属的线程自然结束)。

????????子线程的退出分为显示和隐式退出,隐式退出即线程的执行函数执行 return 返回;显示的退出即调用 pthread_exit。

void pthread_exit(void *status);

(3)获取自身线程的 ID?

pthread_t pthread_self(void)

(4)阻塞等待线程的退出并释放资源

????????通过给定线程 ID,阻塞的等待指定线程的终止,并释放该线程的资源。例如主线程阻塞的等待子线程的关闭,待子线程退出后释放其资源。

#include <pthread.h>
int pthread_join(pthread_t *tid, void **status);

(5)子线程脱离

? ? ? ? pthread_detach 函数,unix网络编程中解释是变为脱离状态(detach),脱离后该线程执行完毕会自动释放资源。如果是可汇合状态(joinable),该线程结束后需要另一个线程执行 pthread_join 来释放其资源。

????????线程有两个状态:joinable 状态和 detach 状态。这个状态可以在 pthread_create 创建线程时设置,默认是 joinable。也就是默认需要你执行 pthread_join() 函数来阻塞的等待子线程的退出,以释放其资源。或者你可以在子线程中使用 pthread_detach(pthread_self()) 函数来改变自身线程的状态为 detach,这样当子线程执行结束后会自动的释放资源。

(6)example(pthread_join主线程阻塞等待后释放资源)

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

void * print(void *arg)
{
    pthread_t id = pthread_self();
    printf("pthread id : %lu\n", id);
    int start = *(int *)arg;
    for (int i = start; i < start + 10; i++)
    {
        sleep(1);
        printf("%d \n", i);
        // 主动终止
        if ( 0 == i % 7) 
            pthread_exit(NULL);
    }
    return NULL;
}

int main(void)
{
    pthread_t tid1, tid2;
    // 定义线程 ID,创建线程,并指定执行函数与参数。
    int start1 = 1;
    pthread_create(&tid1, NULL, print, &start1);
    int start2 = 10;
    pthread_create(&tid2, NULL, print, &start2);
    // 主线程阻塞的等待线程的终止, 即两个线程都执行完毕后主线程执行之后的步骤。
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    return 0;
}

(7)example(pthread_detach子线程结束后自动释放资源)

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

void * print(void *arg)
{
    pthread_detach(pthread_self());
    int start = *(int *)arg;
    for (int i = start; i < start + 10; i++)
    {
        sleep(1);
        printf("%d \n", i);
        if ( 0 == i % 7) 
            pthread_exit(NULL);
    }
    return NULL;
}

int main(void)
{
    pthread_t tid1, tid2;
    int start1 = 1;
    pthread_create(&tid1, NULL, print, &start1);
    int start2 = 10;
    pthread_create(&tid2, NULL, print, &start2);
    sleep(10);
    printf("over\n");
    return 0;
}

4、多线程的数据共享

????????为了保护多线程共享的数据,可以使用互斥锁。任何线程使用共享的数据时必须持有该锁(上锁),其他线程再试图上锁时会被阻塞,直到该数据被持有线程使用完后解锁。

????????互斥锁的类型是 pthread_metux_t,互斥锁变量存在两种初始化方式:静态分配和动态分配。

????????静态分配

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZer;

? ? ? ? 动态分配

pthread_mutex_init(pthread_mutex_t *mutex, 
                    const pthread_mutex_attr_t *mutexattr);
// mutexattr用于指定锁的属性,默认是普通锁

(1)互斥锁的基本使用

#include <pthread.h>
// 阻塞时加锁
int pthread_mutex_lock(pthread_mutex_t *mptr);
// 非阻塞式加锁
int pthread_mutex_trylock(pthread_mutex_t *mptr);
// 释放锁
int pthread_mutex_unlock(pthread_mutex_t *mptr);
// 销毁锁
int pthread_mutex_destroy(pthread_mutex_t *mptr);

// 成功均返回 0
// 非阻塞式加锁在锁被其他线程占用时返回,不阻塞。
// 销毁锁时需确保锁处于 unlock 状态。

(2)example(注意两种初始化互斥锁的方法)

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
// 1 static
//pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
// 2 dynamic
pthread_mutex_t mutex;
// the data to be protected
int count = 0;

void * print(void *arg)
{
    //pthread_detach(pthread_self());
    for (int i = 0; i < 10; i++)
    {
        pthread_mutex_lock(&mutex);
        count++;
        printf("thread %lu -- count is : %d\n", pthread_self(), count);
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}

int main(void)
{
    // 2 dynamic
    pthread_mutex_init(&mutex, NULL);
    pthread_t tid1, tid2;
    pthread_create(&tid1, NULL, print, NULL);
    pthread_create(&tid2, NULL, print, NULL);
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    pthread_mutex_destroy(&mutex);
    printf("over\n");
    return 0;
}
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-12-05 11:51:56  更:2021-12-05 11:52:48 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 11:12:29-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码