一、守护进程
1.守护进程的特点
* 后台服务进程
* 独立于控制终端
* 周期性执行某任务
* 不受用户登录注销影响
* 一般采用以
d
结尾的名字(服务)
2.进程组
* 进程的组长
* 组里边的第一进程
* 进程组的
ID==
进程中的组长的
ID
* 进程中组长的选择
* 进程中的第一个进程
* 进程组ID
的设定
* 进程组的ID
就是组长的进程
ID
3.会话
* 创建一个会话注意事项
* 不能是进程组长(除了只有一个进程组的会话外)
* 创建会话的进程成为新进程组的组长
* 有些lInux
版本需要
root
权限执行此操作
* 创建出的新会话会丢弃原有的控制终端
* 一般步骤;fork ,
父亲死,儿子执行创建会话操作(
setid
)
* 获取进程所属的会话ID :pid_t getsid(pid_t pid);
* 创建一个会话 :pid_t setid(void);
4.创建守护进程模型
* fork子进程,父进程退出
* 子进程创建新会话
* 改变当前工作目录chdir
* 重设文件掩码
* 关闭文件描述符
* 执行核心工作
二、线程
1.主线程和子线程
共享:
* .text
* .bss
* .data
* 堆
* 动态加载区
* 环境变量
* 命令行参数
* -通信:全局变量,堆
不共享:
* 栈:一共五个线程,栈区被平均分成五块
在Linux下: 线程就是进程-轻量级进程
对于内核来说,线程就是进程
多进程和多线程的区别:
多进程: 始终共享的资源 代码、文件描述符、内存映射区--mmap
多线程:始终共享的资源:堆、全局变量,节省资源
安卓线程man page ,命令: sudo apt-get install manpages-posix-dev
查看指定线程的LWP号:
线程号和线程ID是有区别的 ,线程号是给内核看的
查看方式 :找到程序的进程ID->ps -Lf pid
2.线程的创建
int pthread_create( pthread_t *thread), //
线程
ID =
无符号长整型
const pthread_attr_t *attr, //线程属性,NULL
void *(*start_routine)(void *), //线程处理函数
void *arg); //线程处理函数
参数:
pthread:
传出参数,线程创建成功之后,会被设置一个合适的值
attr:
默认传
NULL
start_routine:
子线程的处理函数
arg:
回调函数的参数
返回值:
成功
:0
错误
:
错误号
//perror
不能使用该函数打印错误信息
主线程先退出,子线程会被强制结束
验证线程直接共享全局变量、
示例:
运行结果:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
void *myfun(void *arg)
{
printf("子线程的ID=%ld\n",pthread_self());
return 0;
}
int main()
{
int i;
pthread_t pthid;
int rnt = pthread_create(&pthid,NULL,myfun,NULL);
if(rnt != 0)
{
printf("线程创建失败\n");
printf("错误信息是:%s\n",strerror(rnt));
}
printf("父线程的ID是=%ld\n",pthread_self());
sleep(1);
for(i=0;i<4;i++)
{
printf("i = %d\n",i);
}
return 0;
}
没有sleep时:
有sleep时:
PS:主线程和子线程也会抢夺CPU
3单个线程退出
exit(0):退出整个进程
函数原型: void pthread‐exit(void *retval);
retval
指针:必须指向全局,堆
功能:退出单个线程(没有释放资源)
4.阻塞等待线程退出,获取线程退出状态
功能:阻塞等待子线程,回收子线程资源
函数原型:
int pthread_join(pthread_t pthread, void **retval)
参数:
pthread:
要回收的子线程的
ID
retval:
读取线程退出的携带信息
传出参数
void* ptr;
pthread_join(pthid,&ptr)
;
指向的内存和
pthread_exit
参数指向地址一致
示例:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
int retvl = 100;
void *myfun(void *arg)
{
printf("子线程的ID=%ld\n",pthread_self());
pthread_exit(&retvl);//用退出单个线程函数来退出子线程
printf("如果前面调用了退出单个线程函数,则该语句不执行\n");
return 0;
}
int main()
{
int i;
void *ptr = NULL;
pthread_t pthid;
int rnt = pthread_create(&pthid,NULL,myfun,NULL);
if(rnt != 0)
{
printf("线程创建失败\n");
printf("错误信息是:%s\n",strerror(rnt));
}
printf("父线程的ID是=%ld\n",pthread_self());
pthread_join(pthid,&ptr);
printf("线程退出的携带信息%d\n",*(int *)ptr);
for(i=0;i<4;i++)
{
printf("i = %d\n",i);
}
sleep(1);
return 0;
}
运行结果:
5.线程分离--pthread_detach
函数原型:
int pthread_datach(pthread_t thread);
* 调用该函数之后不需要
pthread_join
* 子线程会自动回收自己的
PCB
6.杀死(取消)线程--pthread_cancel
函数原型:
int pthread_cancel(pthread_t pthread);
使用注意事项:
在要杀死的子线程对应的处理的函数的内部,必须做过一次系统调用 write read printf
int a = 2; int b = a+3;
pthread_testcancel();设置取消点
7.比较两个线程ID是否相等(预留函数) --pthread_equal
函数原型:
int pthread_equal(pthread_t t1,pthread_t t2);
8.通过属性设置线程的分离
1.
线程属性类型:
pthread_attr_t attr;
2.
线程属性操作函数:
对线程属性变量的初始化 :int pthread_attr_init(pthread_attr_t* attr);
设置线程分离属性:int pthread_attr_setdetachstate( pthread_attr_t* attr,int detachstate );
参数:
attr :
线程属性
detachstate:
PTHREAD_CREATE_DETACHED(分离
)
PTHREAD_CREATE_JOINABLE(非分离
)
释放线程资源函数 :int pthread_attr_destroy(pthread_attr_t* attr);
示例:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
int retvl = 100;
void *myfun(void *arg)
{
printf("子线程的ID=%ld\n",pthread_self());
return 0;
}
int main()
{
int i;
pthread_t pthid;
pthread_attr_t attr;
//初始化分离线程
pthread_attr_init(&attr);
//设置分离线程
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
int rnt = pthread_create(&pthid,&attr,myfun,NULL);
if(rnt != 0)
{
printf("线程创建失败\n");
printf("错误信息是:%s\n",strerror(rnt));
}
printf("父线程的ID是=%ld\n",pthread_self());
sleep(1);
for(i=0;i<4;i++)
{
printf("i = %d\n",i);
}
//释放资源
pthread_attr_destroy(&attr);
return 0;
}
运行结果:
9.线程同步
数据混乱的原因:
*操作了共享资源
*CPU调度问题
示例:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
int number;
void *fun1(void *arg)
{
int i;
for(i=0;i<10;i++)
{
int rnt = number;
rnt++;
number = rnt;
printf("fun1 id = %ld,number =%d\n",pthread_self(),number);
}
}
void *fun2(void *arg)
{
int i;
for(i=0;i<10;i++)
{
int rnt = number;
rnt++;
number = rnt;
printf("fun2 id = %ld,number =%d\n",pthread_self(),number);
}
}
int main()
{
pthread_t pid1,pid2;
pthread_create(&pid1,NULL,fun1,NULL);
pthread_create(&pid2,NULL,fun2,NULL);
pthread_join(pid1,NULL);
pthread_join(pid2,NULL);
return 0;
}
运行结果:
10.互斥锁(互斥量)
1.
互斥锁类型
:
创建一把锁:
pthread_mutex_t mutex;
2.
互斥锁的特点:
多个线程访问共享数据的时候是串行的,(可以解决线程同步数据混乱的问题)
3.
使用互斥锁缺点
效率低
4.
互斥锁使用的步骤
创建互斥锁:
pthread_mutex_t mutex;
初始化:
pthread_mutex_init(&mutex,NULL); -- mutex = 1
找到线程共同操作的共享数据
加锁:操作共享资源之前加锁,
pthread_mutex_lock(&mutex); //
阻塞
--mutex = 0
pthread_mutex_trylock(&mutex); //
如果锁上锁直接返回,不阻塞
XXXXXX
共享数据操作
//
临界区 ,越小越好(目的是为了共享数据,如果临界区域里面有过多的非共享数据,会出现bug)
解锁:
pthread_mutex_unlock(&mutex); // -- mutex = 1
阻塞在锁上的线程会被唤醒
销毁:
pthread_mutex_destory(&mutex)
;
互斥锁相关函数:
初始化互斥锁:
pthread_mutex_init( pthread_mutex_t* restrict mutex, const pthread_mutexattr_t* restrict attr, );
销毁互斥锁:
pthread_mutex_destory(pthread_mutex_t* mutex );
加锁:
pthread_mutex_lock(pthread_mutex* mutex)
;
mutex:
没有被锁上:当前线程会将这把锁锁上
被锁上了:当前线程阻塞,锁被打开之后,线程解除阻塞 尝试加锁,失败返回,不阻塞
尝试加锁:
pthread_mutex_trylock(pthread_mutex_t* mutex);
没有锁上:当前线程会被这把锁加锁
如果锁上了:不会阻塞,返回
返回
0
:加锁 成功。没锁上:返回错误号
if( pthread_mutex_trylock(& mutex)==0)
{
//
尝试加锁,并且成功了
//
访问共享资源
XXXXXXXX
}
else
{
//
错误处理
//
或者等待,再次尝试加锁
}
解锁
pthread_mutex_unlock(pthread_mutex_t* mutex)
;
如果我们想要使用互斥锁同步线程所以线程都需要加锁
示例:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
int number;
pthread_mutex_t mutex;
void *fun1(void *arg)
{
int i;
for(i=0;i<10;i++)
{
pthread_mutex_lock(&mutex);
int rnt = number;
rnt++;
number = rnt;
printf("fun1 id = %ld,number =%d\n",pthread_self(),number);
pthread_mutex_unlock(&mutex);
}
}
void *fun2(void *arg)
{
int i;
for(i=0;i<10;i++)
{
pthread_mutex_lock(&mutex);
int rnt = number;
rnt++;
number = rnt;
printf("fun2 id = %ld,number =%d\n",pthread_self(),number);
pthread_mutex_unlock(&mutex);
}
}
int main()
{
pthread_t pid1,pid2;
//初始化锁
pthread_mutex_init(&mutex,NULL);
pthread_create(&pid1,NULL,fun1,NULL);
pthread_create(&pid2,NULL,fun2,NULL);
pthread_join(pid1,NULL);
pthread_join(pid2,NULL);
//销毁锁
pthread_mutex_destroy(&mutex);
return 0;
}
运行结果:
11.死锁
造成死锁的原因:自己锁自己
例如:
for(int i = 0;i<MAX;i++)
{
pthread_mutex_lock(&mutex);//锁在这里已经被使用
pthread_mutex_lock(&mutex);//会阻塞在这里,锁已经被前面使用了,等待前面释放锁
int crt = number;
crt++;
number = crt;
printf("thread A id = %ld,number = %d\n",pthread_self(),number);
pthread_mutex_unlock(&mutex);
usleep(10);
}
PS:操作完锁之后,一定要先解锁,之后再进行操作
线程
1
对共享资源
A
加锁成功
-A
锁
线程
2
对共享资源
B
加锁成功
-B
锁
线程
1需要
访问共享资源
B时
,线程1对
B
锁加锁
-由于线程2还没有释放B锁,
线程
1
阻塞在
B
锁上(等待B锁)
线程2
需要
访问共享资源A
时
,线程2对A
锁加锁
-由于线程1还没有释放B锁,
线程2
阻塞在A
锁上(等待A锁)
如何解决:
-
让线程按照一定的顺序去访问共享资源
-
在访问其他锁的时候,需要先将自己的锁解开
--try_lock
12.读写锁
1.
读写锁是几把锁?
一把锁
pthread_rwlock_t lock;
2.
读写锁的类型;
读锁
-
对内存做读操作
写锁
-
对内存做写操作
4.
读写锁的特性:
* 线程
A
加读锁成功,又来了三个线程,做读操作,可以加锁成功
读共享-
并行处理
* 线程
A
加写锁成功,又来了三个线程,做读操作,三个线程阻塞
写独占
* 线程
A
加读锁成功,又来了
B
线程加写锁阻塞,又来了
C
线程加读锁阻塞
读写不可以同时进行,写的优先级高
5.
读写锁场景练习
* 线程
A
加写锁成功,线程
B
请求读锁
线程
B
阻塞
* 线程
A
持有读锁,线程
B
请求写锁
线程
B
阻塞
* 线程
A
拥有读锁,线程
B
请求读锁
线程
B
加锁
* 线程
A
持有读锁,然后线程
B
请求写锁,然后线程
C
请求读锁
线程
B
阻塞,线程
C
阻塞
线程
B
加锁,线程
C
阻塞
线程
C
加锁
* 线程
A
持有写锁,然后线程
B
请求读锁,然后线程
C
请求写锁
线程
B
阻塞,线程
C
阻塞
线程
C
加锁, 线程
B
阻塞
线程
B
加锁
6.
读写锁的适用场景
互斥锁
-
读写串行
读写锁:
读:并行
写:串行
程序中的读操作
>
写操作的时候
7.
主要操作函数
初始化读写锁
pthread_rwlock_init(pthread_rwlock_t* restrict rwlock, const pthread_rwlockattr_t* restrict attr );
销毁读写锁
pthread_rwlock_destroy(pthread_rwlock_t* rwlock):
加读锁
pthread_rwlock_rdlock(pthread_rwlock_t* rdlock);
阻塞:之前对这把锁加的是写锁的操作
尝试加读锁
pthread_rwlock_tryrdlock(pthread_rwlock_t* rwlock);
加锁成功:返回
0
失败:返回错误号
加写锁
pthread_rwlock_wrlock(pthread_rwlock_t* rwlock);
阻塞:上一次加写锁还没解锁
阻塞:上一次加读锁还没解锁
尝试加写锁
pthread_rwlock_trywrlock(pthread_rwlock_t* rwlock);
解锁
pthread_rwlock_unlock(pthread_rwlock_t* rwlock)
8.
练习
:
三个线程不定时写同一个全局变量,五个线程不定时期读同一全局资源
先不加锁
示例:不加读写锁
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
int number = 0;
void *writeFun(void *arg)
{
while(1)
{
number++;
printf("id = %ld,write number=%d\n",pthread_self(),number);
usleep(1);
}
}
void *readFun(void *arg)
{
while(1)
{
printf("id = %ld,read number = %d\n",pthread_self(),number);
usleep(1);
}
}
int main()
{
int i;
pthread_t p[8];
for(i = 0;i < 3;i++)
{
pthread_create(&p[i],NULL,writeFun,NULL);//多个子线程操作同一个函数,所以只需要用同一个回调函数就好
}
for(i = 3;i<8;i++)
{
pthread_create(&p[i],NULL,readFun,NULL);
}
for(i = 0;i<8;i++)
{
pthread_join(p[i],NULL);
}
}
PS:运行结果会出现,资源混乱的情况,例如上面已经写进去了3948了,但是还是读到3946,加读写锁就不会出现线程同步时,资源混乱的问题
示例:有加读写锁
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
int number = 0;
pthread_rwlock_t rwlock;
void *writeFun(void *arg)
{
while(1)
{
pthread_rwlock_wrlock(&rwlock);//加写锁
number++;
printf("id = %ld,write number=%d\n",pthread_self(),number);
pthread_rwlock_unlock(&rwlock);//解锁
usleep(500);
}
}
void *readFun(void *arg)
{
while(1)
{
pthread_rwlock_rdlock(&rwlock);//加读锁
printf("id = %ld,read number = %d\n",pthread_self(),number);
pthread_rwlock_unlock(&rwlock);
usleep(500);
}
}
int main()
{
int i;
pthread_t p[8];
pthread_rwlock_init(&rwlock, NULL);//初始化读写锁
for(i = 0;i < 3;i++)
{
pthread_create(&p[i],NULL,writeFun,NULL);//多个子线程操作同一个函数,所以只需要用同一个回调函数就好
}
for(i = 3;i<8;i++)
{
pthread_create(&p[i],NULL,readFun,NULL);
}
for(i = 0;i<8;i++)
{
pthread_join(p[i],NULL);
}
pthread_rwlock_destroy(&rwlock);//销毁读写锁
}
13.条件变量
1.
条件变量是锁吗?
不是锁
,但是条件变量能够阻塞线程
使用条件变量
+
互斥量
互斥量:保护一块共享数据
条件变量:引起阻塞 ,如:生产者和消费者模型
2.
条件变量的两个动作
条件不满足,阻塞线程
当条件满足,通知阻塞的线程开始工作
3.
条件变量的类型
pthread_cond_t cond ;
conditon
条件
4.
主要函数:
初始化一个条件变量:
pthread_cond_init(pthread_cond_t * restrict cond, const pthread_condattr_t * restrict attr );
销毁一个条件变量:
pthread_cond_destroy(pthread_cond_t * cond);
阻塞等待一个条件变量:
pthread_cond_wait( pthread_cond_t *restrict cond, pthread_mutex_t * restrict mutex );
* 阻塞线程
* 将已经上锁的mutex
解锁
* 该函数解除阻塞,对互斥锁加锁
限时等待一个条件变量:
pthread_cond_timedwait( pthread_cond_t * restrict cond, pthread_mutex_t * restrict mutex,
const struct timespec * restrict abstime );
唤醒至少一个阻塞在条件变量上的线程:
pthread_cond_signal(pthread_cond_t* cond);
唤醒全部阻塞在条件变量上的线程:
pthread_cond_broadcast(pthread_cond_t * cond)
;
练习 :使用条件变量实现生产者,消费者模型