Daemon(精灵)进程,是Linux中的后台服务进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。一般采用以d结尾的名字,如vsftpd Linux后台的一些系统服务进程,没有控制终端,不能直接和用户交互。不受用户登录、注销的影响,一直在运行着,他们都是守护进程。如:预读入缓输出机制的实现;ftp服务器;nfs服务器等。
第1步:fork子进程,父进程退出
第2步:子进程调用setsid函数创建新会话
第3步:改变当前工作目录chdir
第4步:重设文件掩码 mode & ~umask
第5步:关闭文件描述符
close(STDOUT_FILENO);
close(STDERR_FILENO);
第6步:执行核心工作
编写一个守护进程,每隔2S钟获取一次系统时间,并将这个时间写入磁盘文件。
分析:首先要按照1.3介绍的守护进行的步骤创建一个守护进程
题目要求每隔2S钟,所以需要一个定时器,2S钟触发一次,需要调用setitimer函数创建一个定时器,并且要捕获SIGALRM信号,然后在SIGALRM信号处理函数里面完成获取系统时间,然后将时间写入文件。
用到的主要知识点:
获取系统时间函数time,将time_t类型转换为字符串ctime函数
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
// #include
#include
#define _XOPEN_SOURCE 700
#include
#include
// 编写一个守护进程,每个两s 获取一次系统时间,并将这个时间写入磁盘文件
void myfunc(){
//打开文件
int fd=open("mydemon.log",O_RDWR|O_CREAT);
if(fd<0){
return;
}
//获取当前的 系统时间
time_t t;
time(&t);
//将时间h转成char *
char * p= ctime(&t);
//将时间写入文件
write(fd,p,strlen(p));
close(fd);
return;
}
int main()
{
// 父进程fork() 子进程之后nc父进程自动退出
pid_t pid = fork();
if (pid < 0 || pid > 0)
{
exit(1);
}
// 子进程创建setsid() 函数n创建回话
setsid();
// 改变当前的工作目录
chdir("/home/lixiaoxiang/桌面/log");
// 改变文件掩码 为了
umask(0022);
// 关闭标准输入,输出,和错误输入的文件描述符
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
// 核心操作
// 注册信号处理函数
struct sigaction act;
act.sa_handler = myfunc;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
sigaction(SIGALRM, &act, NULL);
//设置时钟
struct itimerval tm;
tm.it_interval.tv_sec = 2;
tm.it_interval.tv_usec = 0;
tm.it_value.tv_sec = 3;
tm.it_value.tv_usec = 0;
setitimer(ITIMER_REAL, &tm, NULL);
printf("hello\n");
while(1){
sleep(1);
}
}
在linux下,线程最是小的执行单位;进程是最小的分配资源单位。
实际上,无论是创建进程的fork,还是创建线程的pthread_create,底层实现都是调用同一个内核函数 clone。
so:Linux内核是不区分进程和线程的, 只在用户层面上进行区分。所以,线程所有操作函数 pthread_* 是库函数,而非系统调用。
内存地址空间 (.text/.data/.bss/heap/共享库)
优点相对突出,缺点均不是硬伤。Linux下由于实现方法导致进程、线程差别不是很大。
2.5 pthread_create函数
如果任意一个线程调用了exit或_exit,则整个进程的所有线程都终止,由于从main函数return也相当于调用exit,为了防止新创建的线程还没有得到执行就终止,我们在main函数return之前延时1秒,这只是一种权宜之计,即使主线程等待1秒,内核也不一定会调度新创建的线程执行,下一节我们会看到更好的办法。
1 编写程序创建一个线程。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
//线程执行函数
void *mythread(void *arg)
{
//pid 是进程的id pthread_self() 返回自身线程的id
printf("child thread,pid=[%d],id==[%ld]\n", getpid(), pthread_self());
return NULL;
}
int main()
{
//创建子线程
//int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
// void *(*start_routine) (void *), void *arg);
pthread_t thread;
int ret = pthread_create(&thread, NULL, mythread, NULL);//成功返回0 ,否则返回null
if (ret != 0)
{
printf("pthread_create error,[%s]\n", strerror(ret));
return -1;
}
printf("main thread ,pid=[%d],id==[%ld]\n", getpid(), pthread_self());
//目的是让子线程可以执行a起立,否则x子线程没法执行。
sleep(1);
return 0;
}
2 编写程序创建一个线程,并给线程传递一个int参数
3 编写程序创建一个线程,并给线程传递一个结构体参数。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
struct Test{
int data;
char name[64];
};
// 线性执行函数
void *mythread(void *arg){
struct Test *p=(struct Test*) arg;
printf("[%d][%s]\n",p->data,p->name);
printf("child thread ,pid==[%d],id==[%ld]\n",getpid(),pthread_self());
return NULL;
}
int main()
{
int n=90;
struct Test t;
memset(&t,0x00,sizeof(struct Test));
t.data=88;
strcpy(t.name,"xiaowen");
pthread_t thread;
int ret=pthread_create(&thread,NULL,mythread,NULL);
if(ret!=0){
printf("pthread_create error,[%s]\n",strerror(ret));
return -1;
}
printf("main thread ,pid==[%d],id==[%ld]\n",getpid(),pthread_self());
sleep(2);
// int
return 0;
}
4 编写程序,主线程循环创建5个子线程,并让子线程判断自己是第几个子线程。
//循环创建子线程,并且打印是第几个子线程
#include
#include
#include
#include
#include
#include
//线程执行函数
void *mythread(void *arg)
{
int i = *(int *)arg;
printf("[%d]:child thread, pid==[%d], id==[%ld]\n", i, getpid(), pthread_self());
sleep(100);
}
int main()
{
//int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
// void *(*start_routine) (void *), void *arg);
//创建子线程
int ret;
int i = 0;
int n = 5;
int arr[5];
pthread_t thread[5];
for(i=0; i
练习4分析:最后每个子线程打印出来的值并不是想象中的值,比如都是5,分析其原因:
在创建子线程的时候使用循环因子作为参数传递给子线程,这样主线程和多个子线程就会共享变量i(变量i在main函数中定义,在整个进程都一直有效)所以在子线程看来变量i是合法的栈内存空间。
那么为什么最后每个子线程打印出来的值都是5呢?
是由于主线程可能会在一个cpu时间片内连续创建了5个子线程,此时变量i的值变成了5,当主线程失去cpu的时间片后,子线程得到cpu的时间片,子线程访问的是变量i的内存空间的值,所以打印出来值为5.
解决办法:不能使多个子线程都共享同一块内存空间,应该使每个子线程访问不同的内存空间,可以在主线程定义一个数组:int arr[5];,然后创建线程的时候分别传递不同的数组元素,这样每个子线程访问的就是互不相同的内存空间,这样就可以打印正确的值。
如下图:
线程之间(包含主线程和子线程)可以共享同一变量,包含全局变量或者非全局变量(但是非全局变量必须在其有效的生存期内)
在线程中禁止调用exit函数,否则会导致整个进程退出,取而代之的是调用pthread_exit函数,这个函数是使一个线程退出,如果主线程调用pthread_exit函数也不会使整个进程退出,不影响其他线程的执行。
另注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了,栈空间就会被回收。
练习:编写程序测试pthread_exit函数使一个线程退出。
通过程序测试得知,pthread_exit函数只是使一个线程退出,假如子线程里面调用了exit函数,会使整个进程终止;如果主线程调用了pthread_exit函数,并不影响子线程,只是使主线程自己退出。
函数描述:阻塞等待线程退出,获取线程退出状态。其作用,对应进程中的waitpid() 函数。
函数原型:int pthread_join(pthread_t thread, void **retval);
练习:编写程序,使主线程获取子线程的退出状态。
一般先定义void *ptr; 然后pthread_join(threadid, &ptr);
#include
#include
#include
#include
#include
#include
struct Test
{
int data;
char name[64];
};
int g_var = 9;
struct Test t;
void *mythread(void *arg)
{
printf("child thread, pid==[%d], id==[%ld]\n", getpid(), pthread_self());
memset(&t, 0x00, sizeof(t));
t.data = 99;
strcpy(t.name, "xiaoWen");
pthread_exit(&t);
return NULL;
}
int main()
{
pthread_t thread;
int ret = pthread_create(&thread, NULL, mythread, NULL);
if (ret != 0)
{
printf("pthread_create error,[%s]\n", strerror(ret));
return -1;
}
printf("main thread, pid==[%d], id==[%ld]\n", getpid(), pthread_self());
//回收子进程
void *p = NULL;
pthread_join(thread, &p);
struct Test *pt = (struct Test *)p;
printf("child exit status:[%d],[%s],[%p]\n", pt->data, pt->name, p);
system("pause");
return 0;
}
线程分离状态:指定该状态,线程主动与主控线程断开关系。线程结束后,其退出状态不由其他线程获取,而直接自己自动释放。网络、多线程服务器常用。进程若有该机制,将不会产生僵尸进程。僵尸进程的产生主要由于进程死后,大部分资源被释放,一点残留资源仍存于系统中,导致内核认为该进程仍存在。也可使用 pthread_create函数参2(线程属性)来设置线程分离。pthread_detach函数是在创建线程之后调用的。
一般情况下,线程终止后,其终止状态一直保留到其它线程调用pthread_join获取它的状态为止。但是线程也可以被置为detach状态,这样的线程一旦终止就立刻回收它占用的所有资源,而不保留终止状态。不能对一个已经处于detach状态的线程调用pthread_join,这样的调用将返回EINVAL错误。也就是说,如果已经对一个线程调用了pthread_detach就不能再调用pthread_join了
练习:编写程序,在创建线程之后设置线程的分离状态。
#include
#include
#include
#include
#include
#include
void *mythread(void *arg)
{
printf("child thread, pid==[%d], id==[%ld]\n", getpid(), pthread_self());
sleep(10);
return NULL;
}
int main()
{
pthread_t thread;
int ret = pthread_create(&thread, NULL, mythread, NULL);
if (ret != 0)
{
printf("create error,[%s]\n", strerror(ret));
return -1;
}
printf("main thread,pid=[%d],id==[%ld]\n", getpid(), pthread_self());
pthread_detach(thread);
ret = pthread_join(thread, NULL);
if (ret != 0)
{
printf("pthread_join error:[%s]\n", strerror(ret));
}
sleep(1);
system("pause");
return 0;
}
说明:如果线程已经设置了分离状态,则再调用pthread_join就会失败,可用这个方法验证是否已成功设置分离状态。不去掉10s 子进程结束之后就会返回了。
【注意】:线程的取消并不是实时的,而有一定的延时。需要等待线程到达某个取消点(检查点)。
类似于玩游戏存档,必须到达指定的场所(存档点,如:客栈、仓库、城里等)才能存储进度。杀死线程也不是立刻就能完成,必须要到达取消点。
取消点:是线程检查是否被取消,并按请求进行动作的一个位置。通常是一些系统调用creat,open,pause,close,read,write..... 执行命令man 7 pthreads可以查看具备这些取消点的系统调用列表。可粗略认为一个系统调用(进入内核)即为一个取消点。还以通过调用pthread_testcancel函数设置一个取消点。
练习:编写程序,让主线程取消子线程的执行。
先测试一下没有取消点看看能否使线程取消;然后调用pthread_testcancel设置一个取消点,看看能够使线程取消。
//创建子线程
#include
#include
#include
#include
#include
#include
void *mythread(void *arg)
{
while (1)
{
int a;
int b;
//设置取消点
// pthread_testcancel();
printf("-----------\n");
}
return NULL;
}
int main()
{
pthread_t thread;
int ret = pthread_create(&thread, NULL, mythread, NULL);
if (ret != 0)
{
printf("ptread_create error ,[%s]\n", strerror(ret));
return -1;
}
printf("main thread,pid=[%d,id=[%ld]", getpid(), pthread_self());
//取消子线程
pthread_cancel(thread);
pthread_join(thread, NULL);
system("pause");
return 0;
}
函数描述:
注意:这个函数是为了以能够扩展使用的, 有可能Linux在未来线程ID pthread_t 类型被修改为结构体实现。
#include
#include
#include
#include
#include
#include
void *mythread(void *arg)
{
printf("child thread,pid=[%d],id==[%ld]\n", getpid(), pthread_self());
return NULL;
}
int main()
{
pthread_t thread;
int ret = pthread_create(&thread, NULL, mythread, NULL);
if (ret != 0)
{
printf("pthread_create error, [%s]\n", strerror(ret));
return -1;
}
printf("main thread, pid==[%d], id==[%ld]\n", getpid(), pthread_self());
// if (pthread_equal(pthread_self(), pthread_self()) != 0)
if (pthread_equal(thread, pthread_self()) != 0)
{
printf("two thread is same\n");
}
else
{
printf("two thread id is not same\n");
}
sleep(1);
system("pause");
return 0;
}
linux下线程的属性是可以根据实际项目需要,进行设置,之前讨论的线程都是采用线程的默认属性,默认属性已经可以解决绝大多数开发时遇到的问题,如果对程序的性能提出更高的要求,则需要设置线程属性,本节以设置线程的分离属性为例讲解设置线程属性.
分离状态:分离线程没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。应该根据自己的需要,选择适当的分离状态。
第1步:定义线程属性类型类型的变量
第2步:对线程属性变量进行初始化
第3步:设置线程为分离属性
pthread_attr_t *attr,
int detachstate);
注意:这一步完成之后调用pthread_create函数创建线程,
则创建出来的线程就是分离线程;其实上述三步就是
pthread_create的第二个参数做准备工作。
第4步:释放线程属性资源
参数:线程属性
练习:编写程序,创建一个分离属性的线程。
验证:设置为分离属性的线程是不能够被pthread_join函数回收的,
可以通过调用pthread_join函数测试该线程是否已经是分离属性的线程。
线程同步,指一个线程发出某一功能调用时,在没有得到结果之前,该调用不返回。同时其它线程为保证数据一致性,不能调用该功能。
创建两个线程,让两个线程共享一个全局变量int number, 然后让每个线程数5000次数,看最后打印出这个number值是多少?
线程A代码片段:
线程B代码片段:
以上3点中,前两点不能改变,欲提高效率,传递数据,资源必须共享。只要共享资源,就一定会出现竞争。只要存在竞争关系,数据就很容易出现混乱。所以只能从第三点着手解决。使多个线程在访问共享资源的时候,出现互斥。
原子操作指的是该操作要么不做,要么就完成。
使用互斥锁其实是模拟原子操作,互斥锁示意图:
Linux中提供一把互斥锁mutex(也称之为互斥量)。每个线程在对资源操作前都尝试先加锁,成功加锁才能操作,操作结束解锁。资源还是共享的,线程间也还是竞争的,但通过“锁”就将资源的访问变成互斥操作,而后与时间有关的错误也不会再产生了。
线程1访问共享资源的时候要先判断锁是否锁着,如果锁着就阻塞等待;若锁是解开的就将这把锁加锁,此时可以访问共享资源,访问完成后释放锁,这样其他线程就有机会获得锁。
pthread_mutex_t mutex; 变量mutex只有两种取值1、0。
const pthread_mutexattr_t *restrict attr);
restrict关键字:只用于限制指针,告诉编译器,所有修改该指针指向内存中内容的操作,只能通过本指针完成。不能通过除本指针以外的其他变量或指针修改互斥量mutex的两种初始化方式:
pthead_mutex_t muetx = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_init(&mutex, NULL)
应该注意:图中同一时刻,只能有一个线程持有该锁,只要该线程未完成操作就不释放锁。
使用互斥锁之后,两个线程由并行操作变成了串行操作,效率降低了,但是数据不一致的问题得到解决了。
练习:使用互斥锁解决两个线程数数不一致的问题。
代码片段:在访问共享资源前加锁,访问结束后立即解锁。锁的“粒度”应越小越好。
总结:使用互斥锁之后,两个线程由并行变为了串行,效率降低了,但是可以使两个线程同步操作共享资源,从而解决了数据不一致的问题。
int pthread_mutex_lock(pthread_mutex_t *mutex);