继续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
工欲善其事,必先利其器,首先安装一个好用的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即可。
更多关于Ubuntu 18.04安装codeBlocks的方法,可以参考:https://blog.csdn.net/AAMahone/article/details/86531631
由于是虚拟机,可能界面比例很小,导致 代码编辑区很小,看起来很不舒服,大概我知道的有两种解决:
方法一:把虚拟机界面最大化
我的虚拟机是VirualBox,要想调整虚拟机界面大小,要先安装 增强模式,如果之前安装过了,就可以直接调整界面大小了。安装增强模式也很简单,在虚拟机菜单栏设备
–安装增强功能
,安装完成后,返回桌面,会发现多了一个光盘,点击光盘,在标题栏的下方右侧,也就是右上角,有一个 运行程序,点击,然后重启Ubuntu即可。一定要点击运行程序,要不然增强功能不生效!
方法二:调整codeblocks各个栏目的大小
codeblocks的GUI设计很人性化,每一个栏都可以调整大小,同时也可以在菜单栏view
中关闭不需要显示的部分,例如我个人的喜欢布局如下:
这个功能其实是拼写纠错功能,可能是在Ubuntu下对中文还没完全适配好,导致输入中文就出现红色波浪线,尤其是在printf中和注释里,让人眼花
菜单栏Plugins
–Manage plugins
找到 SpellChecker
disable它,然后重启codeblocks就行了。
实验说明:
学会通过基本的Linux进程控制函数,由父进程创建子进程,并实现协同工作。创建两个进程,让子进程读取一个文件,父进程等待子进程读完文件后继续执行。
解决方案:
进程协同工作就是要协调好两个或两个以上的进程,使之安排好先后次序并依次执行,可以用wait()或者waitpid()函数来实现这一点。当只需要等待任一子进程运行结束时,可在父进程中调用wait()函数。若需要等待某一特定子进程的运行结果时,需调用waitpid()函数,它是非阻塞型函数。
程序框架
当fork( )调用成功后,父子进程完成各自的任务,但当父进程的工作告一段落,需要用到子进程的结果时,它就停下来调用wait( ),一直等到子进程运行结束,然后利用子进程的结果继续执行,这样就圆满地解决了父子进程间的协同工作问题。
指导书都写得这么详细了,那么直接开整吧
有关创建project的操作和Windows下的操作一样,菜单栏File
–new
–Project
–Console application
我在/home/ronglin/code/gnu/
新建一个project文件夹,用来存放C项目,要选择c代码,本次实验项目名称为create_process
然后一路next就行了。当然因为本次实验实际上只需要一个.c文件就行了。也可以只是新建一个.c文件,菜单栏File
–new
–File..
–C/C++ source
然后也是填写信息,一路next就行了,但是缺点就是单独一个.c/cpp文件是没办法用codeblocks的调试功能的。
在Sources的main.c中写代码,如果是main.cpp说明新建的是一个c++程序,虽然大概差不多,但是还是推荐c和c++代码分离。
因为子进程还要打开一个文件,我在工程目录下新建一个text.txt
文件用于测试。
然后就是头秃的过程了。
#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;
}
由于是用的scanf,遇到空格就读入完毕,所以结果呈现如上图。
因为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
输出结果同上。
实验说明:
了解线程与进程之间的数据共享关系。创建一个线程,在线程中更改进程中的数。
解决方案:
在进程中定义共享数据,在线程中直接引用并输出该数据。
程序框架:
创建第二个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,菜单栏Settings
–Compiler
,左侧Global compiler settings
,中间Linker settings
在左侧Link libraries
中 add 刚刚复制好的路径,右侧Other linker options
,输入-lpthread
然后ok就行了.
运行成功.
实验说明
多线程实现单词统计工具。
解决方案
区分单词原则:凡是一个非字母或数字的字符跟在字母或数字的后面,那么这个字母或数字就是单词的结尾。
允许线程使用互斥锁来修改临界资源,确保线程间的同步与协作。如果两个线程需要安全地共享一个公共计数器,需要把公共计数器加锁。线程需要访问称为互斥锁的变量,它可以使线程间很好地合作,避免对于资源的访问冲突。
程序框架
创建第三个project–words_counter
,先在根目录下新建两个txt文件,一共3+6=9个单词
#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函数传参数,菜单栏Project
–Set programs’ arguments…
,左侧Global compiler settings
,选中Debug,在下边的Program arguments
中添加要参入的参数./text1.txt ./text2.txt
.然后ok.
然后运行查看结果:
成功了,=w=