Linux 学习记录25(进程线程篇)
一、守护进程(daemon)
所谓守护进程,其实就是一个服务,随着系统的启动而启动,随着系统的结束而结束,不依赖于终端而存在因为在终端上运行的进程,随着终端被关闭,进程也随之关闭而且一般不会被打断,需要将其执行放在根目录下
1. 守护进程创建流程
1. 创建一个孤儿进程
2. 重新设置孤儿进程的SID(会话id)和组id (让进程独立出来)
3. 修改守护进程的目录为根目录,当然,也可以不修改,但是一旦所在的目标被删除,守护进程也被删除了
4. 修改创建文件的掩码给最大权限 (umask(0))
5. 将标准输入,标准输出、标准出错重定向到某个文件 (dup2)
6. 对应的文件就是日志文件
2. 创建守护进程所需的api
(1. 创建会话
函数原型:pid t setsid(void);
功能:创建一个会话,将当前的进程的id设置为pgid,和sid
参数1:无
返回值:成功返回会话的id失败返回-1并置位错误码
(2. 切换目录
函数原型:int chdir(const char *path);
功能:切换目录
参数1:要切换的目录,是一个字符串
返回值:成功返回失败返回-1并置位错误码
(3. 更改掩码
函数原型:mode t umask(mode t mask);
功能:更改掩码
参数1:要修改的值
返回值:永远成功,不会失败
(4. 获取程序中最大文件描述符
函数原型:int getdtablesize(void);
功能:回去程序中能打开的最大文件描述符的值(1024)
参数1:无
返回值:成功返回当前进程文件描述符的最大个数,不会失败
3. 创建守护进程
(1. 创建一个孤儿进程
int main(int argc, char const *argv[])
{
pid_t pid = fork();
if(pid < 0)
{//如果创建失败
printf("Error:");
return -1;
}else if(pid == 0)
{//子进程
while (1);//让子进程一直执行
}else
{//关闭父进程,让子进程成为孤儿进程
exit(EXIT_SUCCESS);
}
return 0;
}
(2. 设置孤儿进程的SID(会话ID)和组ID,使进程独立出来
int main(int argc, char const *argv[])
{
pid_t pid = fork();
if(pid < 0)
{//如果创建失败
printf("Error:");
return -1;
}else if(pid == 0)
{//子进程
/*设置孤儿进程的SID(会话ID)和组ID,使进程独立出来*/
setsid();//设置SID(会话ID)和组ID
while (1);//让子进程一直执行
}else
{//关闭父进程,让子进程成为孤儿进程
exit(EXIT_SUCCESS);
}
return 0;
}
(3. 更改进程的目录到根目录
int main(int argc, char const *argv[])
{
pid_t pid = fork();
if(pid < 0)
{//如果创建失败
printf("Error:");
return -1;
}else if(pid == 0)
{//子进程
/*设置孤儿进程的SID(会话ID)和组ID,使进程独立出来*/
setsid();//设置SID(会话ID)和组ID
/*更改进程的目录到根目录*/
chdir("/");
while (1);//让子进程一直执行
}else
{//关闭父进程,让子进程成为孤儿进程
exit(EXIT_SUCCESS);
}
return 0;
}
(4. 修改创建文件的掩码给最大权限
int main(int argc, char const *argv[])
{
pid_t pid = fork();
if(pid < 0)
{//如果创建失败
printf("Error:");
return -1;
}else if(pid == 0)
{//子进程
/*设置孤儿进程的SID(会话ID)和组ID,使进程独立出来*/
setsid();//设置SID(会话ID)和组ID
/*更改进程的目录到根目录*/
chdir("/");
/*修改创建文件的掩码给最大权限*/
umask(0);
while (1);//让子进程一直执行
}else
{//关闭父进程,让子进程成为孤儿进程
exit(EXIT_SUCCESS);
}
return 0;
}
(5. 将文件打开的所有文件描述符关闭
int main(int argc, char const *argv[])
{
pid_t pid = fork();
if(pid < 0)
{//如果创建失败
printf("Error:");
return -1;
}else if(pid == 0)
{//子进程
/*设置孤儿进程的SID(会话ID)和组ID,使进程独立出来*/
setsid();//设置SID(会话ID)和组ID
/*更改进程的目录到根目录*/
chdir("/");
/*修改创建文件的掩码给最大权限*/
umask(0);
/*将文件打开的所有文件描述符关闭*/
for (int i = 0; i < getdtablesize(); i++)
{
close(i);
}
while (1);//让子进程一直执行
}else
{//关闭父进程,让子进程成为孤儿进程
exit(EXIT_SUCCESS);
}
return 0;
}
(6. 将标准输入,输出,出错重定向到日志文件
int main(int argc, char const *argv[])
{
pid_t pid = fork();
if(pid < 0)
{//如果创建失败
printf("Error:");
return -1;
}else if(pid == 0)
{//子进程
/*设置孤儿进程的SID(会话ID)和组ID,使进程独立出来*/
setsid();//设置SID(会话ID)和组ID
/*更改进程的目录到根目录*/
chdir("/");
/*修改创建文件的掩码给最大权限*/
umask(0);
/*将文件打开的所有文件描述符关闭*/
for (int i = 0; i < getdtablesize(); i++)
{
close(i);
}
/*将标准输入,输出,出错重定向到日志文件*/
int fd;
if((fd = open("log.txt",Ap))
{
perror("open: ");
}
dup2(fd, STDIN_FILENO);
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
while (1);//让子进程一直执行
}else
{//关闭父进程,让子进程成为孤儿进程
exit(EXIT_SUCCESS);
}
return 0;
}
(7. 执行守护进程
int main(int argc, char const *argv[])
{
pid_t pid = fork();
if(pid < 0)
{//如果创建失败
printf("Error:");
return -1;
}else if(pid == 0)
{//子进程
/*设置孤儿进程的SID(会话ID)和组ID,使进程独立出来*/
setsid();//设置SID(会话ID)和组ID
/*更改进程的目录到根目录*/
chdir("/");
/*修改创建文件的掩码给最大权限*/
umask(0);
// /*将文件打开的所有文件描述符关闭*/
// for (int i = 0; i < getdtablesize(); i++)
// {
// close(i);
// }
/*将标准输入,输出,出错重定向到日志文件*/
int fd;
if((fd = open("log.txt",Ap,0664)) == -1)
{
perror("open");
return -1;
}
// dup2(fd, STDIN_FILENO);
dup2(fd, STDOUT_FILENO);
// dup2(fd, STDERR_FILENO);
/*对应文件就是日志文件,执行服务功能*/
int line = 1;
while(1)
{//执行守护进程
printf("%d : 这是守护进程\r\n",line++);
if(line==10) break;
}
while(1);
}else
{//关闭父进程,让子进程成为孤儿进程
exit(EXIT_SUCCESS);
}
return 0;
}
运行需要管理员权限
在目录下查看日志文件
查看守护进程
需要管理员权限关闭守护进程
二、多线程
1. 多线程概念
头文件: #include <pthread.h>
- 多线程(LWP轻量版的进程) :线程是粒度更小的处理单元
进程是资源分配的最小单位,线程是调度器进行调度的最小单位
- 线程共享进程的资源,多进程拥有自己独立的资源
- 线程几乎不占用资源,只占用的很小的有关执行状态的资源,大概在(8K)左右
- 线程由于共用进程的资源,所以多线程没有多进程安全,使用多线程是因为开销较小
- 在一个进程内,至少有一个线程(主线程)
- 因为线程操作函数需要依赖于第三方库,所以,想使用线程处理函数,需要安装对应的库
指令:sudo apt-get install manpages-posix-dev
2. 多线程创建(pthread_create)
由于线程处理函数是第三方库提供,所以,编译时需要引入相关库,需要加上 -pthread
函数原型:int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
功能:创建线程
参数1:要创建的线程的线程号(以参数的形式返回)
参数2:线程的属性,一般填NULL即可
参数3:线程处理函数,是一个参数为void*类型,返回值为void"类型的函数指针
需要传递进来的是线程处理函数的函数名
参数4:向线程处理函数中传递的参数
返回值:成功返回0,失败返回错误码
====================================================================
/*定义线程处理函数*/
void *task(void *arg)
{
printf("子线程\r\n");
while(1);
}
int main(int argc, char const *argv[])
{
pthread_t thid;//线程号
if(pthread_create(&thid, NULL,task,NULL) != 0)
{//如果创建失败
perror("pthread");
return -1;
}
printf("主线程\r\n");
while(1);//防止进程结束
return 0;
}
运行:
3. 多线程内存问题
同一个进程内的所有线程共享进程内的所有资源
/*定义一个全局变量*/
int num = 1000;
/*定义线程处理函数*/
void *task1(void *arg)
{
num += 100;
printf("子线程1 num: %d\r\n",num);
while(1);
}
/*定义线程处理函数*/
void *task2(void *arg)
{
num -= 200;
printf("子线程2 num: %d\r\n",num);
while(1);
}
int main(int argc, char const *argv[])
{
pthread_t tid1;//线程号
pthread_t tid2;//线程号
if(pthread_create(&tid1, NULL,task1,NULL) != 0)
{//如果创建失败
perror("pthread task1");
return -1;
}
if(pthread_create(&tid2, NULL,task2,NULL) != 0)
{//如果创建失败
perror("pthread task2");
return -1;
}
num -= 300;
printf("主线程 num: %d\r\n",num);
while(1);//防止进程结束
return 0;
}
输出>>
子线程1 num: 1100
子线程2 num: 700
主线程 num: 900
4. 多线程执行顺序
多线程之间执行顺序没有要求,遵时间片轮询,上下文切换原则
5. 多线程获取线程ID(pthread_self)
函数原型:
功能:获取当前线程的1线程号
参数1:无
返回值:成功返回当前线程的线程号,不会失败
====================================
/*定义线程处理函数*/
void *task(void *arg)
{
printf("子线程\r\n");
printf("子程号:%#lx\r\n",pthread_self());
while(1);
}
int main(int argc, char const *argv[])
{
pthread_t thid;//线程号
if(pthread_create(&thid, NULL,task,NULL) != 0)
{//如果创建失败
perror("pthread");
return -1;
}
printf("主线程\r\n");
printf("主线程的子线程号:%#lx\r\n",thid);
printf("主线程的线程号:%#lx\r\n",pthread_self());
while(1);//防止进程结束
return 0;
}
输出>>
主线程
主线程的子线程号:0x7ff1da18e700
主线程的线程号:0x7ff1da9ae740
子线程
子程号:0x7ff1da18e700
6. 多线程退出(pthread_exit)
如果在子线程中使用exit(EXIT_SUCCESS),则,整个进程都会退出
所以需要使用专门退出线程的函数
函数原型:void pthread_exit(void *retval);
功能:退出所在的线程
参数1:退出线程时的状态,一般填NULL即可
返回值:无
7. 多线程资源回收(pthread_join)
函数原型:int pthread_join(pthread_t thread, void **retval);
功能:阻塞等待回收线程的资源,并为线程收尸
参数1:线程号
参数2:线程退出时的状态,一般未NULL
返回值:成功返回0,失败返回错误码。
-===================================
int main(int argc, char const *argv[])
{
pthread_t tid1;//线程号
pthread_t tid2;//线程号
if(pthread_create(&tid1, NULL,task1,NULL) != 0)
{//如果创建失败
perror("pthread task1");
return -1;
}
if(pthread_create(&tid2, NULL,task2,NULL) != 0)
{//如果创建失败
perror("pthread task2");
return -1;
}
printf("主线程 \r\n");
pthread_join(tid1,NULL);//阻塞等待线程回收
pthread_join(tid2,NULL);
pthread_exit(EXIT_SUCCESS);
return 0;
}
8. 多线程发信号(pthread_cancel)
(1. pthread_cancel
函数原型:int pthread_cancel(pthread_t thread);
功能:给一个线程给另一个线程发送一个取消信号
参数1:线程号
返回值:成功返回0,失败返回非0的错误码
(2. pthread_setcancelstate
函数原型:int pthread_setcancelstate(int state, int *oldstate);
功能:
参数1:状态
PTHREAD_CANCEL_ENABLE :可用状态(可被打断)
PTHREAD_CANCEL_DISABLE :不可以状态(不可被打断)
参数2:旧的状态,一般NULL
返回值:成功返回0,失败返回错误码
9. 多线程分离态(pthread_detach)
对于线程而言,有两个状态,分别是结合态和分离态,默认是结合态,结合态的线程,结束后需要使用pthread_join回收资源
分离态的线程,不需要使用pthread_join回收资源
函数原型:int pthread_detach(pthread_t thread);
功能:将线程标记为分离态
参数1:线程号
返回值:成功返回e,失败返回错误码
三、多线程互斥
1. 多线程竞态现象
- 临界资源:在多线程中,多个线程能够共享的资源
- 临界区:访问临界资源的代码就叫临界区
- 互斥:同一时间内只允许一个线程拥有临界资源
- 同步:要求安顺序执行,安顺序访问临界资源
当一个临界资源被多个线程同时访问时,这种现象叫做竞态
要解决这个问题就需要使用线程的同步互斥原则
2. 多线程互斥
与斥是多个线程共同强占临界资源的过程中,只有抢占成功,就可以操作临界资源,如果没有抢占成功,就不能操作临界资源,这里没有体现按顺序进行使用临界资源问题,可能在某个线程执行多次后,才抢占到临界资源,也有可能永远枪不到,还有可能是多个线程同时抢到,为了解决这个问题,我们引入互斥机制,互斥机制解决多个线程同时抢到临界资源问题,但是不解决是否抢占成功问题
3. 线程互斥锁API
#include <pthread.h>
//1、定义一个互斥锁
pthread_mutex_t mutex;
//2、c初始化互斥锁
//静态初始化
pthread_mutex_t fastmutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t recmutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
pthread_mutex_t errchkmutex = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP;
//动态初始化一个互斥锁
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);
功能:初始化一个互斥锁
参数1:互斥锁指针
参数2:互斥锁属性一般为:NULL
返回值:总是返回0
//3、加锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
功能:获取锁
参数:互斥锁
返回值:成功返回0,失败返回非0的错误码
int pthread_mutex_trylock(pthread_mutex_t *mutex);
//跟lock功能一致
//4、释放锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
功能:释放锁
参数:互斥锁
返回值:成功返回0,失败返回非0的错误码
//5、销毁锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
功能:销毁锁
参数:互斥锁
返回值:成功返回0,失败返回非0的错误码
使用
pthread_mutex_t mutex;//定义互斥锁
/*定义线程处理函数*/
void *task1(void *arg)
{
pthread_mutex_lock(&mutex);//给临界区上锁
printf("子进程任务结束 关闭进程\r\n");
pthread_mutex_unlock(&mutex);//给临界区解锁锁
pthread_exit(NULL);//退出线程
}
int main(int argc, char const *argv[])
{
pthread_t tid1;//线程号
pthread_mutex_init(&mutex,NULL);//初始化互斥锁
if(pthread_create(&tid1, NULL,task1,NULL) != 0)
{//如果创建失败
perror("pthread task1");
return -1;
}
printf("主线程 \r\n");
pthread_join(tid1,NULL);//阻塞等待线程回收
printf("线程1回收完成\r\n");
//6、销毁锁
pthread_mutex_destroy(&mutex);
pthread_exit(EXIT_SUCCESS);
return 0;
}
四、
思维导图
练习
1. 使用多线程拷贝图片(文件)
(1. 主函数
#include "public.h"
int main(int argc, char const *argv[])
{
/*获取文件信息*/
file.filename_cp[0]='.';
file.filename_cp[1]='/';
file.filename[0]='.';
file.filename[1]='/';
strcpy(file.filename_cp+2,argv[1]);
strcpy(file.filename+2,argv[2]);
struct stat sb;
/*获取文件属性*/
stat(file.filename_cp, &sb);//打开文件
file.size = sb.st_size;
pthread_t tid1;//线程号
pthread_t tid2;//线程号
if(pthread_create(&tid1, NULL,task1,&file) != 0)
{//如果创建失败
perror("pthread task1");
return -1;
}
if(pthread_create(&tid2, NULL,task2,&file) != 0)
{//如果创建失败
perror("pthread task2");
return -1;
}
printf("主线程 \r\n");
pthread_join(tid1,NULL);//阻塞等待线程回收
printf("线程1回收完成\r\n");
pthread_join(tid2,NULL);
printf("线程2回收完成\r\n");
pthread_exit(EXIT_SUCCESS);
return 0;
}
(2. 子线程1
/*定义线程处理函数*/
void *task1(void *arg)
{
file_type* file = (file_type*)arg;
int fp1;
int fp2;
int i = 0;
char ch = 0;
if((fp1 = open(file->filename_cp,R)) == -1)
{//文件打开失败
perror("open file");
pthread_exit(NULL);//退出线程
}
if((fp2 = open(file->filename,Wp,0664)) == -1)
{//文件打开失败
perror("open file");
pthread_exit(NULL);//退出线程
}
for(i = 0;i<(file->size/2);i++)
{
read(fp1,&ch,1);//读文件
write(fp2,&ch,1);
}
close(fp1);
close(fp2);
printf("子进程任务结束 关闭进程\r\n");
pthread_exit(NULL);//退出线程
}
(3. 子线程2
/*定义线程处理函数*/
void *task2(void *arg)
{
file_type* file = (file_type*)arg;
int fp1;
int fp2;
int i = 0;
char ch = 0;
if((fp1 = open(file->filename_cp,R)) == -1)
{//文件打开失败
perror("open file");
pthread_exit(NULL);//退出线程
}
if((fp2 = open(file->filename,Wp,0664)) == -1)
{//文件打开失败
perror("open file");
pthread_exit(NULL);//退出线程
}
/*两个光标向后偏移*/
lseek(fp1,file->size-(file->size/2),SEEK_SET);
lseek(fp2,file->size-(file->size/2),SEEK_SET);
for(i = 0;i<(file->size-(file->size/2));i++)
{
read(fp1,&ch,1);//读文件
write(fp2,&ch,1);
}
close(fp1);
close(fp2);
printf("子进程任务结束 关闭进程\r\n");
pthread_exit(NULL);//退出线程
}
(4. 头文件
#ifndef __PUBLIC_H_
#define __PUBLIC_H_
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <sys/wait.h>
#include <pthread.h>
#define R O_RDONLY
#define W O_WRONLY|O_CREAT|O_TRUNC
#define A O_WRONLY|O_CREAT|O_APPEND
#define Rp O_RDWR
#define Wp O_RDWR|O_CREAT|O_TRUNC
#define Ap O_RDWR|O_CREAT|O_APPEND
typedef struct
{
int size;//文件大小
char filename_cp[50];//被拷贝的文件名
char filename[50];//文件2
}file_type;
extern file_type file;
/*定义线程处理函数*/
void *task1(void *file);
/*定义线程处理函数*/
void *task2(void *file);
#endif