Linux学习之C语言的进程与线程编程

前言

继续Linux的学习,学到了Linux系统下的进程与线程的概念,布置了有关其的实验题,用C语言编程启动进程线程,习惯了Java多线程编程,这次在Linux下玩一玩C语言进程线程编程。
本文原创,创作不易,转载请注明!!!
本文链接
个人博客:https://ronglin.fun/?p=155
PDF链接:见博客网站
CSDN: https://blog.csdn.net/RongLin02/article/details/115419994

准备工作

环境与配置

主机:联想Y7000P;64位windows10;CPU:i7-9750H;显卡:GTX 1660 Ti;内存:16G
虚拟机:Ubuntu 18.04 LTS;硬盘100G;内存4G;64位;4核心
Linux内核:5.11.8

安装IDE

工欲善其事,必先利其器,首先安装一个好用的C语言IDE,C语言在Linux下的IDE有很多,比如geany,codeBlocks等等,当然也有大佬用文本编辑器vim nano gedit等直接写,然后用gcc编译也是可以的。我这里用Windows下我比较喜欢的一个编译器codeBlocks。

方法一

2022.2.28可用
输入以下代码

sudo add-apt-repository universe
sudo apt update
sudo apt install codeblocks

方法二

为了更方遍的调试,先安装一下GCC。输入:

sudo apt install gcc

然后安装codeBlocks和其所依赖的库,输入:

sudo add-apt-repository ppa:damien-moore/codeblocks-stable
sudo apt-get update
sudo apt-get install codeblocks
sudo apt-get install codeblocks-contrib

等待其自己安装完就行了。
如果上面那个ppa库没法用还可以用下边这个

sudo add-apt-repository ppa:pasgui/ppa
sudo apt-get update
sudo apt-get install codeblocks
sudo apt-get install codeblocks-contrib

补充资料:
PPA:PPA全称为 Personal Package Archives(个人软件包档案),是 Ubuntu Launchpad 网站提供的一项服务,当然不仅限于 Launchpad 。它允许个人用户上传软件源代码,通过 Launchpad 进行编译并发布为二进制软件包,作为 apt/新立得源供其他用户下载和更新。
sudo add-apt-repository ppa:pasgui/ppa这句代码,会在/etc/apt/sources.list.d目录下添加PPA源,供我们使用,所以如若想删除添加的PPA源,到/etc/apt/sources.list.d目录下,删除对应的文件即可。

安装完成后,直接在终端中输入codeblocks,弹出来界面,点击ok即可。
Linux学习之C语言的进程与线程编程_第1张图片
更多关于Ubuntu 18.04安装codeBlocks的方法,可以参考:https://blog.csdn.net/AAMahone/article/details/86531631

题外话

界面太小

由于是虚拟机,可能界面比例很小,导致 代码编辑区很小,看起来很不舒服,大概我知道的有两种解决:
方法一:把虚拟机界面最大化
我的虚拟机是VirualBox,要想调整虚拟机界面大小,要先安装 增强模式,如果之前安装过了,就可以直接调整界面大小了。安装增强模式也很简单,在虚拟机菜单栏设备安装增强功能,安装完成后,返回桌面,会发现多了一个光盘,点击光盘,在标题栏的下方右侧,也就是右上角,有一个 运行程序,点击,然后重启Ubuntu即可。一定要点击运行程序,要不然增强功能不生效!
方法二:调整codeblocks各个栏目的大小
codeblocks的GUI设计很人性化,每一个栏都可以调整大小,同时也可以在菜单栏view中关闭不需要显示的部分,例如我个人的喜欢布局如下:
Linux学习之C语言的进程与线程编程_第2张图片

中文下方红色波浪线

这个功能其实是拼写纠错功能,可能是在Ubuntu下对中文还没完全适配好,导致输入中文就出现红色波浪线,尤其是在printf中和注释里,让人眼花
菜单栏PluginsManage plugins 找到 SpellChecker disable它,然后重启codeblocks就行了。
Linux学习之C语言的进程与线程编程_第3张图片

创建进程

实验简述

实验说明:
学会通过基本的Linux进程控制函数,由父进程创建子进程,并实现协同工作。创建两个进程,让子进程读取一个文件,父进程等待子进程读完文件后继续执行。
解决方案:
进程协同工作就是要协调好两个或两个以上的进程,使之安排好先后次序并依次执行,可以用wait()或者waitpid()函数来实现这一点。当只需要等待任一子进程运行结束时,可在父进程中调用wait()函数。若需要等待某一特定子进程的运行结果时,需调用waitpid()函数,它是非阻塞型函数。
程序框架
Linux学习之C语言的进程与线程编程_第4张图片
当fork( )调用成功后,父子进程完成各自的任务,但当父进程的工作告一段落,需要用到子进程的结果时,它就停下来调用wait( ),一直等到子进程运行结束,然后利用子进程的结果继续执行,这样就圆满地解决了父子进程间的协同工作问题。

源代码

指导书都写得这么详细了,那么直接开整吧
有关创建project的操作和Windows下的操作一样,菜单栏FilenewProjectConsole application
我在/home/ronglin/code/gnu/新建一个project文件夹,用来存放C项目,要选择c代码,本次实验项目名称为create_process
Linux学习之C语言的进程与线程编程_第5张图片
然后一路next就行了。当然因为本次实验实际上只需要一个.c文件就行了。也可以只是新建一个.c文件,菜单栏FilenewFile..C/C++ source然后也是填写信息,一路next就行了,但是缺点就是单独一个.c/cpp文件是没办法用codeblocks的调试功能的。
在Sources的main.c中写代码,如果是main.cpp说明新建的是一个c++程序,虽然大概差不多,但是还是推荐c和c++代码分离。
因为子进程还要打开一个文件,我在工程目录下新建一个text.txt文件用于测试。
Linux学习之C语言的进程与线程编程_第6张图片
然后就是头秃的过程了。

fork()创建

#include
#include
#include

#define COLMAX 1024 //每一个字符串的最大长度(列)
#define ROWMAX 64   //字符串最大个数(行)

/*
本代码实现用子进程打开同目录下的text.txt文件
并且父进程输出内容
*/

int main()
{
    int p_id = -1;

    if( (p_id=fork()) == -1 ) //子进程创建失败
    {
        printf("Process_1 Create Error\n");
    }
    else if(p_id == 0)   //子进程部分
    {
        printf("%d Process Start Work\n",getpid());

        char text[ROWMAX][COLMAX]={0};
        FILE *fp = fopen("./text.txt","r+");//打开文件
        if( fp == NULL )//打开文件失败
        {
            printf("Fail to open file!\n");
        }
        else
        {
            int i=0;
            while( (fscanf(fp,"%s",text[i])) != EOF)
            {
                printf("%s\n",text[i]);
                i++;
                sleep(1);   //等待1s方便查看输出
            }
        }

        fclose(fp);
        exit(0);
    }
    //父进程部分

    waitpid(p_id,NULL,0);//阻塞等待

    printf("%d proccess is end\n",p_id);

    return 0;
}

Linux学习之C语言的进程与线程编程_第7张图片
由于是用的scanf,遇到空格就读入完毕,所以结果呈现如上图。

vfork()创建

因为fork()创建的子进程其实是父进程数据的copy,也就是说父子进程的数据并不是共享的,子进程只是父进程的翻版,类似于c语言函数中的传递形式参数,要想数据共享,其实也有很多方法,资料书上提到很多CLONE_开头的标志位就是控制哪些东西是父子进程共享的。不过有更简单的vfork()函数,其实本质上是一样的,都是调用内核函数do_fork()。接下来就实现,子进程读取文件,然后父进程输出,需要注意的是,不论多进程还是多线程,数据一旦共享,会涉及到安全问题,不过vfork()创建子进程之后,父进程就会阻塞,所以就没安全问题了。
直接看源码吧

#include
#include
#include

#define COLMAX 1024 //每一个字符串的最大长度(列)
#define ROWMAX 64   //字符串最大个数(行)

/*
本代码实现用子进程打开同目录下的text.txt文件
并且父进程输出内容
*/

int main()
{
    int count=0;
    int p_id = -1;
    char text[ROWMAX][COLMAX]={0};

    if( (p_id=vfork()) == -1 ) //子进程创建失败
    {
        printf("Process_1 Create Error\n");
    }
    else if(p_id == 0)   //子进程部分
    {
        printf("%d Process Start Work\n",getpid());

        FILE *fp = fopen("./text.txt","r+");//打开文件
        if( fp == NULL )//打开文件失败
        {
            printf("Fail to open file!\n");
        }
        else
        {
            while( (fscanf(fp,"%s",text[count++])) != EOF);
        }

        fclose(fp);
        exit(0);
    }
    //父进程部分

    for(int i=0;i

输出结果同上。

线程共享进程中的数据

实验简述

实验说明:
了解线程与进程之间的数据共享关系。创建一个线程,在线程中更改进程中的数。
解决方案:
在进程中定义共享数据,在线程中直接引用并输出该数据。
程序框架:
Linux学习之C语言的进程与线程编程_第8张图片

源代码

创建第二个project–share_thread,因为有一部分Java多线程开发的基础,所以直接开整。

#include 
#include 
#include 

static int shdata=4;

void *create(void *arg)
{
    printf("new pthread...\n");
    printf("shdata data = %d \n",shdata);
    return (void *)(0);
}


int main()
{

    pthread_t mythread ;
    int error = 0;

    error = pthread_create(&mythread,NULL,create,NULL);
    if(error)
    {
        printf("pthread_create is not created...\n");
        return -1;
    }

    sleep(1);
    printf("pthread_create is ok...\n");

    return 0;
}

写完代码发现,编译一直报错,查阅资料才知道,pthread库不是Linux系统默认的库,需要使用库libpthread.a
如果是用gcc指令编译则应该输入这个
gcc test.c -lpthread -o pthread

如果是codeblocks就添加库就行了,首先需要在终端中输入:

locate libpthread.a

先找一下 文件在哪
在这里插入图片描述
有两个,一般用/usr目录下的,将/usr/lib/x86_64-linux-gnu/libpthread.a复制下来。
codeblocks,菜单栏SettingsCompiler,左侧Global compiler settings,中间Linker settings
在左侧Link libraries中 add 刚刚复制好的路径,右侧Other linker options,输入-lpthread
然后ok就行了.
Linux学习之C语言的进程与线程编程_第9张图片
Linux学习之C语言的进程与线程编程_第10张图片
运行成功.

多线程实现单词统计工具

实验简述

实验说明
多线程实现单词统计工具。
解决方案
区分单词原则:凡是一个非字母或数字的字符跟在字母或数字的后面,那么这个字母或数字就是单词的结尾。
允许线程使用互斥锁来修改临界资源,确保线程间的同步与协作。如果两个线程需要安全地共享一个公共计数器,需要把公共计数器加锁。线程需要访问称为互斥锁的变量,它可以使线程间很好地合作,避免对于资源的访问冲突。
程序框架
Linux学习之C语言的进程与线程编程_第11张图片

源代码

创建第三个project–words_counter,先在根目录下新建两个txt文件,一共3+6=9个单词
Linux学习之C语言的进程与线程编程_第12张图片

#include 
#include
#include

pthread_mutex_t counter_clock = PTHREAD_MUTEX_INITIALIZER;
static int total_words = 0;

void *count_words(void*);


int main(int ac,char *av[ ])
{

    if(ac !=3)
    {
        printf("Usage:%s file1 filc2\n",av[0]);
        exit(1);
    }
    int error_1=0,error_2=0;
    pthread_t conuter_1,conuter_2;

    /*分别以av[1]和av[2]作为参数,创建两个线程t1和t2*/
    error_1 = pthread_create(&conuter_1,NULL,count_words,av[1]);
    error_2 = pthread_create(&conuter_2,NULL,count_words,av[2]);

    if(error_1 || error_2)
    {
        printf("pthread_create is not created...\n");
        return -1;
    }

    /*让线程t1和t2进入等待态*/
    error_1 = pthread_join(conuter_1,NULL);
    error_2 = pthread_join(conuter_2,NULL);

    if(error_1 || error_2)
    {
        printf("pthread_join is failed...\n");
        return -1;
    }

    /*输出统计出来的单词总数*/
    printf("Number of words is %d\n",total_words);
    return 0;
}


void *count_words(void *f)
{
    char *filename = (char *)f;
    FILE *fp;
    int c,prevc='\0';

    if( (fp=fopen(filename,"r")) != NULL) //文件不为空
    {
        while((c=getc(fp)) != EOF)  //输入
        {
            if(!isalnum(c) && isalnum(prevc))   //所传的字符一是字母和数字 && 一个是其他字符
            {
                pthread_mutex_lock(&counter_clock); //为了线程安全上锁
                total_words++;
                pthread_mutex_unlock(&counter_clock);   //解锁
            }
            prevc = c;
        }
        fclose(fp);
    }
    else
    {
        perror(filename);   //输出错误信息
    }
    return NULL;
}

代码见上,然后就是给main函数传参数,菜单栏ProjectSet programs’ arguments…,左侧Global compiler settings,选中Debug,在下边的Program arguments中添加要参入的参数./text1.txt ./text2.txt.然后ok.
Linux学习之C语言的进程与线程编程_第13张图片
然后运行查看结果:
Linux学习之C语言的进程与线程编程_第14张图片
成功了,=w=

你可能感兴趣的:(Linux,linux,c语言,多线程,多进程)