Linux进程与线程的编程(C/C++版)
文章目录
Linux的线程基础知识请跳转到 Linux命令行学习第12小节了解相关基础。
C/C++编译程序的实现请了解相关gcc和g++的命令行操作。
通过C/C++语言实现多线程操作,主要过程是了解进程和线程的状态,和C/C++的相关函数。
为了更进一步的使用C/C++编程多线程程序,希望大家先了解其中有关进程/线程操作的函数:
fork()函数
作用:创建一个新进程
创建过程:一个进程,包括代码、数据和分配给进程的资源。fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。
创建结果:在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,fork返回新创建子进程的进程ID。我们可以通过fork返回的值来判断当前进程是子进程还是父进程。
例如我们创建下列.cpp文件:
则其编译生成后的可运行文件在终端运行后的结果如下:
通过上面的实例,可以看出fork()的作用。
vfork()函数
不同于vfork()函数,其创建的子进程共父进程的空间,创建子进程后,父进程会被阻塞直到执行exec()或exit()。
即其子进程会和父进程使用的是同样的内存资源,子进程对父进程的改变同样会影响父进程
如下列.cpp文件的运行文件的结果:
注意vfork必须有exit()或exec()跳出,不然由于return则父程序会被直接破坏掉,从而产生灾难性的错误。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mygYapIe-1618187971926)(C:\Users\wizard\AppData\Roaming\Typora\typora-user-images\1618185690922.png)]
clone()函数
其作用是创建线程,但后面介绍的pthread_create()函数有类似的作用,故在这里不具体展开。
exec()函数
作用:使进程引入新程序
我们将后缀l,v,p,e添加在exec()函数后,可使其具有一定的功能
p:可直接执行PATH变量下的文件(/bin/*),无需在加地址
l:可以接收以逗号为分隔的参数列表,以NULL指针作为结束标注
v:可以接收以逗号为分隔的字符串,以NULL指针作为结束标注
e:允许改变子进程环境
进程等待
当我们使用fork()时,其创建的子进程的顺序和父进程的顺序时无法预测的,所以我们需要使用wait()和waitpid()函数来避免错误
其中的waitpid()用来指定特定的pid进程,可以使用getpid()来获取进程。
进程终止(销毁)
可以使用exit(0),前面的例子已经使用多次,不再赘述。
线程的创建和删除
线程包提供了一组函数用来创建,删除线程
线程创建:
可以使用clone()函数(创建内核支持的线程)和pthread_create()函数(创建内核不可见的线程,由线程库调度)创建线程。这里我们使用pthread_create()函数进行创建。
结果为:
Pthread_create is created...
休眠
sleep(time)//休眠time秒
例如上面的c文件,我们将其休眠
结果为:
Pthread_create is created... #注意虽然结果相同,但是是五秒后打印的结果
线程同步
这里使用公共计数器和互斥锁来实现线程之间的同步
我们用简单的买票系统来实验互斥锁:
未加锁:
结果:
我们发现,虽然只有20张票,但最终已经卖了23张,这显然不正确。
而当我们使用互斥锁后:
问题得到了解决。
那么互斥锁如何使用呢?
我们通过Pthread_mutex_lock()和Pthread_mutex_unlock()进行锁和解锁。
在这里,我们将互斥锁Pthread_mutex_lock(ticket_sum)和Pthread_mutex_unlock(ticket_sum)放在在票卖出语句前后,从而实现在同一时间只有一个进程引用票数。
线程结束
exit();在前面的程序中都有所体现,这里不再赘述。
线程等待
pthread_join(线程,NULL)函数,让主线程等待子线程完成再结束。这个函数防止了子线程还没有运行完主线程就结束的危险。
学以致用,我们使用上述方法,设计三个与进程/线程有关的程序:
通过进程的创建,用子进程打印超级玛丽。
思路:使用vfork()函数创建进程,从而实现先于父进程读取文件,读后再让父进程运行,但这样的话子父进程会调用同一资源而造成不必要的麻烦,故不考虑,或使用fork()和wait()实现使父进程等待子进程,此时调用的是两部分资源,可行。
代码部分:
#include <iostream>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <wait.h>
using namespace std;
int main()
{
int pid=fork();
if(pid<0)
printf("创建失败了");
else
{
if(pid==0)
{
cout << " ********" << endl;
cout << " ************" << endl;
cout << " ####....#." << endl;
cout << " #..###.....##...." << endl;
cout << " ###.......###### ### ### ### ###" << endl;
cout << " ........... #...# #...# #...# #...#" << endl;
cout << " ##*####### #.#.# #.#.# #.#.# #.#.#" << endl;
cout << " ####*******###### #.#.# #.#.# #.#.# #.#.#" << endl;
cout << " ...#***.****.*###.... #...# #...# #...# #...#" << endl;
cout << " ....**********##..... ### ### ### ###" << endl;
cout << " ....**** *****...." << endl;
cout << " #### ####" << endl;
cout << " ###### ######" << endl;
cout << "############################################################## ##################################" << endl;
cout << "#...#......#.##...#......#.##...#......#.##------------------# #...#......#.##------------------#" << endl;
cout << "###########################################------------------# ###############------------------#" << endl;
cout << "#..#....#....##..#....#....##..#....#....##################### #..#....#....#####################" << endl;
cout << "########################################## #----------# ############## #----------#" << endl;
cout << "#.....#......##.....#......##.....#......# #----------# #.....#......# #----------#" << endl;
cout << "########################################## #----------# ############## #----------#" << endl;
cout << "#.#..#....#..##.#..#....#..##.#..#....#..# #----------# #.#..#....#..# #----------#" << endl;
cout << "########################################## ############ ############## ############" << endl;
}
else
{
wait(NULL);//等待子进程 byAEI
cout<<"子进程打印了超级玛丽"<<endl;
}
}
return 0;
}
结果:
首先子进程打印出超级玛丽:
后出现父进程结果:
子进程打印了超级玛丽 #虽然紧接着超级玛丽的打印而打印出来,但要知道是两个进程
线程共享进程数据
思路:使用线程创建函数创建10个线程,并定义共享数据来共享。
#include <iostream>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <wait.h>
using namespace std;
string s="create thread";
void *create(void *arg)
{
cout<<s<<endl;
return 0;
}
int main(void)
{
pthread_t tidp[10];
for(int i=0;i<10;i++)
{
int error;
error=pthread_create(&tidp[i],NULL,create,NULL);
if(error !=0)
{
cout<<"thread"<<i<<"create erroro!\n";
return -1;
}
}
return 0;
}
实现结果:
为什么会出现有的线程输出在同一行,有的却单空出一行呢?因为线程在不上锁的情况下,会出现抢占资源的情况,从而导致可能一个线程的执行并不是先后执行的,而是谁抢到了CPU谁继续执行。
那么如何解决这样的问题呢?我们可以在每个线程创建后,执行sleep()函数,先让主进程睡一会,在这期间,新创建的线程已经执行完,从而近似的解决了线程冲突。
但这样的操作会让程序执行时间大大增加,所以我们可以考虑另一种方案,即互斥锁的操作。
多线程实现单词统计(利用互斥锁)
思路:为了实现多线程统计单词,我们不仅需要多线程之间引用公共资源,还需要为线程上锁,同时为了不让主线程在子线程前提前结束,还需要使用线程等待函数让主线程等待子线程完结。
代码:
结果:
虽然两个线程执行顺序可能不同,每一个线程也也可能和另一个线程交替运行,但由于添加了互斥锁,共享资源单词数的计算不会受影响。
同时若我们忽略其中一个线程,则会产生只统计其中一个文件单词数的现象: