【8】进程
(1)进程的概念
进程是一个独立的可调度的任务,进程是一个抽象实体。当系统在执行某个程序时,分配和释放的各种资源,进程是一个程序的一次执行的过程。
进程和程序的区别:
程序是
静态的,它是一些保存在磁盘上的指令的有序集合,没有任何执行的概念。
进程是一个
动态的概念,它是程序执行的过程,包括创建、调度和消亡
进程是程序执行和资源管理的最小单位 。
(2)进程的内存管理
在32位操作系统里面,当进程创建或者开启的时候,都会给其分配4G虚拟内存,将4G的虚拟内存分为1G的内核空间,3G用户空间,其中用户空间是私有的,内核空间是所有进程公有的 。
(3)进程号
当进程创建或者开启的时候,系统会随机分配一个非负整数用于标识当前进程,称之为进程号。
0 内核进程,由内核创建
1 init进程,是所有进程的祖先
(4)Linux系统中的进程类型
交互进程:
该类进程是由shell控制和运行的。交互进程既可以在前台运行,也可以在后台运行。
批处理进程:
该类进程不属于某个终端,它被提交到一个队列中以便顺序执行。
守护进程:
该类进程在后台运行。它一般在Linux启动时开始执行,系统关闭时才结束。
(5)内核中的进程结构 – task_struct
每一个进程开启时,都会有一个进程表项或者叫做进程控制块,来标识一个进程,记录当前进程的相关信息,在/usr/src/linux-headers-3.2.0-29-generic-pae/include/linux下的sched.h头文件里面定义了task_struct结构体。
(6)进程相关命令
ps 查看系统中的进程
ps aux 查看当前系统中所有的进程的信息
ps ajx 可以查看系统中所有进程的父进程的进程号
top 动态显示系统中的进程
nice 按用户指定的优先级运行进程
renice 改变正在运行进程的优先级
kill 向一个进程发送信号
kill -l 查看当前系统定义的所有的信号
kill -9 pid 杀死一个进程
kill -18 pid 将停止的进程回到后台复执行
./a.out & 当前程序在后台执行
bg 将停止的进程在后台执行
fg 把后台运行的进程放到前台运行
(7)进程运行状态
D: 不可中断的静止,等待
R: 正在执行中,运行态
S: 阻塞状态,睡眠态
T: 暂停执行,停止态 (程序运行时,按下ctrl+z)
Z: 不存在但暂时无法消除,僵尸态
W: 没有足够的内存分页可分配
+: 前台进程
<: 高优先级的进程
N: 低优先级的进程
L: 有内存分页分配并锁在内存中,多线程的
s: 会话组组长
#include
int main(int argc, const char *argv[])
{
//int a;
//scanf阻塞等待数据的接收,是睡眠态 S
//scanf("%d", &a);
//while循环一直执行,是运行态 R
while(1)
{
printf("hello world
");
sleep(1);
}
//使用键盘输入ctrl+z,使得进程变为停止态 T
return 0;
}
(8)进程的调度机制
时间片的轮转,上下文切换。
(9)函数
1 – fork( )
#include
pid_t fork(void);
功能:创建一个子进程
参数:
无
返回值:
成功:
>0 子进程的进程号,父进程的代码区
=0 子进程的代码区
#include
#include
int main(int argc, const char *argv[])
{
pid_t pid;
//使用fork函数创建子进程
//父子进程运行时没有先后顺序
//父子进程是来回交替执行的
if((pid = fork()) < 0)
{
perror("fail to fork");
return -1;
}
else if(pid > 0) //父进程的代码区
{
printf("This is parent process
");
sleep(1);
printf("1111111111
");
}
else //子进程的代码区
{
printf("This is child process
");
sleep(1);
printf("22222222222
");
}
//父子进程运行的代码不仅限于返回值判断,返回值只是决
定父子进程运行不同的代码,但是程序如果没有结束,则会继续执行
printf("hello world
");
while(1)
;
return 0;
}
#include
#include
int a = 100;
int main(int argc, const char *argv[])
{
int b = 200;
static int c = 300;
pid_t pid;
//使用fork创建子进程后,子进程会继承父进程fork之前的所有的虚拟空间,
//但是fork之后,父子进程的空间独立,所以不管父进程还是子进程怎么修改数据
//都不会影响对方
if((pid = fork()) < 0)
{
perror("fail to fork");
return -1;
}
else if(pid > 0) //父进程的代码区
{
printf("This is parent process
");
a = 999;
printf("%d - %d - %d
", a, b, c);
printf("%p - %p - %p
", &a, &b, &c);
}
else //子进程的代码区
{
sleep(1);
printf("This is child process
");
printf("%d - %d - %d
", a, b, c);
printf("%p - %p - %p
", &a, &b, &c);
}
while(1)
;
return 0;
}
#include
#include
#include
#include
#include
int main(int argc, const char *argv[])
{
pid_t pid;
if((pid = fork()) < 0)
{
perror("fail to fork");
return -1;
}
else if(pid > 0) //父进程的代码区
{
printf("This is parent process
");
int fd;
if((fd = open("file.txt", O_WRONLY | O_CREAT | O_TRUNC, 0664)) < 0)
{
perror("fail to open");
return -1;
}
write(fd, "hello world
", 12);
}
else //子进程的代码区
{
sleep(1);
printf("This is child process
");
int fd;
if((fd = open("file.txt", O_RDONLY)) < 0)
{
perror("fail to open");
return -1;
}
char buf[32] = {};
read(fd, buf, 32);
printf("buf = %s
", buf);
}
while(1)
;
return 0;
}
2 – getpid( ) / getppid( )
#include
#include
pid_t getpid(void);
获取当前进程的进程号
pid_t getppid(void);
获取当前进程的父进程的进程号
#include
#include
#include
int main(int argc, const char *argv[])
{
pid_t pid;
if((pid = fork()) < 0)
{
perror("fail to fork");
return -1;
}
else if(pid > 0) //父进程的代码区
{
printf("This is parent process
");
printf("pid = %d
", pid);
printf("parent: pid = %d, ppid = %d
", getpid(), getppid());
}
else //子进程的代码区
{
sleep(1);
printf("This is child process
");
printf("child: pid = %d, ppid = %d
", getpid(), getppid());
}
while(1)
;
return 0;
}
3 – exit和_exit
#include
void exit(int status);
功能:结束一个进程
参数:
status:进程的退出状态,可以被wait函数获取
一般返回0和非0
0 正常退出
非0 错误退出
返回值:
无
#include
void _exit(int status);
功能:结束一个进程
参数:
status:进程的退出状态,可以被wait函数获取
一般返回0和非0
0 正常退出
非0 错误退出
返回值:
无
exit:刷新缓冲区
_exit:不刷新缓冲区
#include
#include
#include
void myfun()
{
printf("nihao beijing");
//return:在主函数里面可以退出整个进程,但是在子函数里面只能退出当前函数
//return ;
//exit:不管放在哪,都表示退出一个进程
//exit(0);
//_exit:功能与exit一样,但是不会刷新缓冲区
_exit(0);
}
int main(int argc, const char *argv[])
{
printf("hello world
");
myfun();
printf("welcome to hqyj
");
return 0;
}
4 – wait( ) / waitpid( )
#include
pid_t wait(int *stat_loc);
功能:阻塞等待一个子进程的退出状态
参数:
stat_loc:保存子进程的退出状态
返回值:
成功:退出的子进程的进程号
失败:-1
pid_t waitpid(pid_t pid, int *stat_loc, int options);
功能:阻塞等待一个子进程的退出状态
参数:
pid:指定的子进程
-1 任意一个子进程
>0 接收指定子进程的退出状态
0 接收当前进程所在的进程组里面所有的子进程
<-1 接收进程组号等于当前参数的进程组里面的所有的子进程
stat_loc:保存子进程的退出状态
options:模式
0 阻塞
WNOHANG 非阻塞
返回值:
成功:退出的子进程的进程号
失败:-1
wait(NULL) <==> waitpid(-1, NULL, 0)
#include
#include
#include
#include
int main(int argc, const char *argv[])
{
pid_t pid;
if((pid = fork()) < 0)
{
perror("fail to fork");
exit(1);
}
else if(pid > 0) //父进程的代码区
{
printf("This is parent process
");
//wait(NULL);
waitpid(-1, NULL, 0);
}
else //子进程的代码区
{
printf("This is child process
");
sleep(3);
printf("hello world
");
exit(0);
}
return 0;
}
(10)linux特殊的进程
孤儿进程
守护进程
僵尸进程
(11)孤儿进程 orphan
当创建子进程后父进程退出,子进程没有退出,此时的子进程称之为孤儿进程此时孤儿进程的父进程为init进程,资源由init进程回收。
#include
#include
#include
int main(int argc, const char *argv[])
{
pid_t pid;
//孤儿进程:父进程退出,子进程没有退出,子进程称之为孤儿进程
if((pid = fork()) < 0)
{
perror("fail to fork");
exit(1);
}
else if(pid > 0) //父进程
{
printf("parent: pid = %d, ppid = %d
", getpid(), getppid());
sleep(3);
exit(0);
}
else //子进程
{
printf("child: pid = %d, ppid = %d
", getpid(), getppid());
sleep(5);
printf("child: pid = %d, ppid = %d
", getpid(), getppid());
while(1)
;
}
return 0;
}
(12)守护进程 daemon
守护进程,也就是通常所说的Daemon进程,是Linux中的后台服务进程。它是一个生存期较长的进程,通常独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件守护进程常常在系统启动时开始运行,在系统关闭时终止Linux系统有很多守护进程,大多数服务都是用守护进程实现的。
在Linux中,每一个系统与用户进行交流的界面称为终端。从该终端开始行的进程都会依附于这个终端,这个终端称为这些进程的控制终端。当控制终端被关闭时,相应的进程都会被自动关闭。
守护进程能够突破这种限制,它从开始运行,直到整个系统关闭才会退出。
如果想让某个进程不会因为用户或终端的变化而受到影响,就必须把这个进
程变成一个守护进程
Linux守护进程编写步骤
创建孤儿进程
在子进程中创建新会话 setsid( )
改变当前目录为根目录 chdir( )
重设文件权限掩码 umask( )
关闭文件描述符
#include
#include
#include
#include
#include
#include
int main(int argc, const char *argv[])
{
//创建守护进程
//第一步:创建孤儿进程
pid_t pid;
if((pid = fork()) < 0)
{
perror("fail to fork");
exit(1);
}
else if(pid > 0) //父进程
{
exit(0);
}
else //子进程
{
//第二步:在子进程里面创建新的会话,使得当前子进程会会话组组长
setsid();
//第三步:改变当前进程的工作目录为根目录,防止当前目录被删除
chdir("/");
//第四步:重设文件权限掩码,使得当前进程根加灵活的操作每个文件
umask(0);
//第五步:关闭所有的文件描述符
int fds = getdtablesize();
int i;
for(i = 0; i < fds; i++)
{
close(i);
}
//让守护进程执行相应的操作
int fd;
if((fd = open("test.txt", O_WRONLY | O_CREAT | O_APPEND, 0664)) < 0)
{
perror("fail to open");
exit(1);
}
while(1)
{
write(fd, "hello world
", 12);
sleep(1);
}
}
return 0;
}
(13)僵尸进程 zombie
当创建子进程时,如果子进程退出但是父进程没有退出,此时子进程的资源没有释放,称之为僵尸进程,比较浪费资源。
#include
#include
#include
#include
#include
int main(int argc, const char *argv[])
{
//创建一个僵尸进程
pid_t pid;
if((pid = fork()) < 0)
{
perror("fail to fork");
exit(1);
}
else if(pid > 0) //父进程
{
printf("This is parent process
");
//处理僵尸进程的方式
//方法1:父进程退出,僵尸进程由init进程回收
//方法2:使用wait函数处理僵尸进程
wait(NULL);
//方法3:使用信号
while(1)
;
}
else //子进程
{
exit(0);
}
return 0;
}
【9】exec函数族
(1)性质
exec函数族提供了
一种在进程中启动另一个程序执行的方法。它可
以根据指定的文件名或目录名找到可执行文件,并用它来取代原调
用进程的数据段、代码段和堆栈段。在执行完之后,原调用进程的
内容除了进程号外,其他全部都被替换了。
可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚
本文件。
(2)函数
#include
int execl(const char *path, const char *arg0, ... /*, (char *)0 */);
int execv(const char *path, char *const argv[]);
int execlp(const char *file, const char *arg0, ... /*, (char *)0 */);
int execvp(const char *file, char *const argv[]);
功能:在一个进程里面执行另一个命令或者程序
参数:
path:程序或者命令的路径
返回值:
成功:当前进程的进程号
失败:-1
l 使用每一个字符串来保存将要执行的命令或者程序,最后一个必须加NULL
例如:
“ls”, “-l”, NULL
v 使用指针数组保存将要执行的命令或者程序,最后一个为NULL
char *s[] = {“ls”, “-s”, NULL}
p 如果不加p,则命令的路
#include
#include
#include
#include
#include
int main(int argc, const char *argv[])
{
pid_t pid;
if((pid = fork()) < 0)
{
perror("fail to fork");
exit(1);
}
else if(pid > 0) //父进程
{
printf("this is parent process
");
wait(NULL);
}
else //子进程
{
printf("this is child process
");
//不带p执行命令时必须时绝对路径
//if(execl("/bin/ls", "ls", "-l", NULL) < 0)
//如果带p,既可以是相对路径,也可以是绝对路径
//if(execlp("ls", "ls", "-l", NULL) < 0)
//含有v的函数使用指针数组保存命令
//char *s[] = {"ls", "-l", NULL};
//if(execvp("ls", s) < 0)
//if(execl("../hello", "hello", NULL) < 0)
if(execl("shell.sh", "shell.sh", NULL) < 0)
{
perror("fail to execl");
exit(1);
}
printf("hello world
");
}
return 0;
}
(3)system( )
#include
int system(const char *command);
功能:执行一个命令或者程序
参数:
command:保存命令的字符串
返回值:
成功:命令的状态
失败:-1
#include
#include
int main(int argc, const char *argv[])
{
printf("*****************************
");
system("ls -l");
system("./hello");
system("./shell.sh");
printf("*****************************
");
return 0;
}
【9】线程
(1)线程概念
由于进程的地址空间是私有的,因此在
进程间上下文切换时,系统开销比较大为了提高系统的性能,许多操作系统规范里引入了
轻量级进程的概念,也被称为
线程。
在同一个进程中创建的线程
共享该进程的地址空间Linux里同样用task_struct来描述一个线程。线程和进程都参与统一的调度通常线程指的是共享相同地址空间的多个任务。
进程是资源管理的最小单位,线程是程序执行的最小单位。
总结:
当执行多个任务时,如果采用多进程,进程间是来回切换执行的,每一个进程拥有私有的用户空间,所以进程间来回切换时,用户空间也会来回切换,这样做会很浪费cpu资源,所以采用多线程,线程是轻量级的进程,线程在进程内创建,一个进程可以创建多个线程,一个进程里面的所有的线程共有同一个进程的用户空间的资源,所以如果线程间来回切换,资源不用切换,极大的节省cpu的消耗。但是如果当前进程结束,则所有的线程会强制退出。
(2)主控线程和子线程
默认情况下,一个进程当中只有一个线程,叫做
主控线程,由主控线程创建的线程称之为
子线程,不管是主控线程还是子线程,都附属于当期的进程。
(3)函数
1 – pthread_create( )
#include
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
功能:创建一个新的线程
参数:
thread:线程的id
attr:线程属性结构体,为NULL表示以默认的属性创建
start_routine:线程处理函数
arg:给start_routine函数传参
返回值:
成功:0
失败:非0
#include
#include
#include
//由于线程库不是当前系统本身有的,所以需要手动链接库文件 gcc *.c -lpthread
void *pthread_fun1(void *arg)
{
printf("子线程1正在运行...
");
sleep(1);
printf("1111111111111
");
}
void *pthread_fun2(void *arg)
{
printf("子线程2正在运行...
");
sleep(1);
printf("2222222222222
");
}
int main(int argc, const char *argv[])
{
//当前进程会先创建一个线程,称之为主控线程
printf("主控线程正在运行...
");
//创建子线程
//当使用pthread_create创建子线程时,
//子线程没有先后执行顺序
//线程间也是来回切换执行的
pthread_t thread1, thread2;
if(pthread_create(&thread1, NULL, pthread_fun1, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
if(pthread_create(&thread2, NULL, pthread_fun2, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
//如果进程结束,则所有的线程都会退出
while(1)
;
return 0;
}
#include
#include
#include
int a = 100;
//可以将子线程看作就是一个函数,
//全局变量子线程共有,如果一个改变其值,则另一个获取改变的值
//局部变量可以通过第四个参数传入线程处理函数
//每一个子线程内部拥有私有的栈区、堆区和静态区
void *pthread_fun1(void *arg)
{
printf("子线程1正在运行...
");
printf("1: a = %d
", a++);
printf("1: b = %d
", *(int *)arg);
*(int *)arg = 300;
}
void *pthread_fun2(void *arg)
{
sleep(1);
printf("子线程2正在运行...
");
printf("2: a = %d
", a);
printf("2: b = %d
", *(int *)arg);
}
int main(int argc, const char *argv[])
{
int b = 200;
printf("主控线程正在运行...
");
pthread_t thread1, thread2;
if(pthread_create(&thread1, NULL, pthread_fun1, (void *)&b) != 0)
{
perror("fail to pthread_create");
exit(1);
}
if(pthread_create(&thread2, NULL, pthread_fun2, (void *)&b) != 0)
{
perror("fail to pthread_create");
exit(1);
}
while(1)
;
return 0;
}
2 – pthread_self( )
#include
pthread_t pthread_self(void);
功能:获取当前线程的id
参数:
无
返回值:
成功:线程的id
#include
#include
#include
void *pthread_fun1(void *arg)
{
printf("子线程1:%#lx 正在运行...
", pthread_self());
}
void *pthread_fun2(void *arg)
{
printf("子线程2:%#lx 正在运行...
", pthread_self());
}
int main(int argc, const char *argv[])
{
printf("主控线程:%#lx 正在运行...
", pthread_self());
pthread_t thread1, thread2;
if(pthread_create(&thread1, NULL, pthread_fun1, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
if(pthread_create(&thread2, NULL, pthread_fun2, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
printf("thread1 = %#lx
", thread1);
printf("thread2 = %#lx
", thread2);
//如果进程结束,则所有的线程都会退出
while(1)
;
return 0;
}
3 – pthread_exit()
#include
void pthread_exit(void *retval);
功能:退出一个线程
参数:
retval:当前线程返回退出的结果,可以被pthread_join函数接收到
返回值:
无
#include
#include
#include
void *pthread_fun1(void *arg)
{
while(1)
{
printf("子线程1:%#lx 正在运行...
", pthread_self());
sleep(1);
}
}
void *pthread_fun2(void *arg)
{
while(1)
{
printf("子线程2:%#lx 正在运行...
", pthread_self());
sleep(1);
//pthread_exit(NULL);
}
}
int main(int argc, const char *argv[])
{
printf("主控线程:%#lx 正在运行...
", pthread_self());
pthread_t thread1, thread2;
if(pthread_create(&thread1, NULL, pthread_fun1, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
if(pthread_create(&thread2, NULL, pthread_fun2, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
printf("thread1 = %#lx
", thread1);
printf("thread2 = %#lx
", thread2);
//当关闭主控线程,子线程还可一继续运行,因为进程没有结束
pthread_exit(NULL);
//如果进程结束,则所有的线程都会退出
while(1)
;
return 0;
}
4 – pthread_cancel( )
#include
int pthread_cancel(pthread_t thread);
功能:在一个线程里面销毁另一个线程
参数:
thread:要销毁的线程的id
返回值:
成功:0
失败:非0
#include
#include
#include
pthread_t thread1, thread2;
void *pthread_fun1(void *arg)
{
while(1)
{
printf("子线程1:%#lx 正在运行...
", pthread_self());
sleep(1);
}
}
void *pthread_fun2(void *arg)
{
while(1)
{
printf("子线程2:%#lx 正在运行...
", pthread_self());
sleep(1);
pthread_cancel(thread1);
}
}
int main(int argc, const char *argv[])
{
printf("主控线程:%#lx 正在运行...
", pthread_self());
if(pthread_create(&thread1, NULL, pthread_fun1, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
if(pthread_create(&thread2, NULL, pthread_fun2, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
printf("thread1 = %#lx
", thread1);
printf("thread2 = %#lx
", thread2);
//sleep(5);
//pthread_cancel(thread1);
//如果进程结束,则所有的线程都会退出
while(1)
;
return 0;
}
(4)线程的结合态和分离态
linux线程执行和windows不同,pthread有两种状态:可
结合的(joinable)或者是分离的(detached),线程默认创建为可结合态。
如果线程是joinable状态,当线程函数自己返回退出时或pthread_exit时都不会释放线程所占用堆栈和线程描述符(总计8K多)。只
有当你调用了pthread_join之后这些资源才会被释放。
若是detached状态的线程,这些资源在
线程函数退出时或pthread_exit时自动会被释放。
(5)pthread_join( )
#include
int pthread_join(pthread_t thread, void **retval);
功能:阻塞等待一个子线程的退出
参数:
thread:要退出的线程的id
retval:子线程的退出状态,通过pthread_exit传入的退出的值
返回值:
成功:0
失败:非0
#include
#include
#include
void *pthread_fun1(void *arg)
{
printf("子线程1:%#lx 正在运行...
", pthread_self());
sleep(4);
//pthread_exit("The thread1 has quited");
static int a = 200;
printf("&a = %p
", &a);
pthread_exit((void *)&a);
//static char s[] = "hello world";
//printf("s = %p
", s);
//pthread_exit((void *)s);
}
void *pthread_fun2(void *arg)
{
printf("子线程2:%#lx 正在运行...
", pthread_self());
sleep(5);
pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
printf("主控线程:%#lx 正在运行...
", pthread_self());
pthread_t thread1, thread2;
if(pthread_create(&thread1, NULL, pthread_fun1, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
if(pthread_create(&thread2, NULL, pthread_fun2, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
printf("thread1 = %#lx
", thread1);
printf("thread2 = %#lx
", thread2);
//阻塞等待一个子线程的退出
#if 0
char *s;
pthread_join(thread1, (void **)&s);
printf("s = %p
", s);
printf("s = %s
", s);
#endif
#if 1
int *a;
pthread_join(thread1, (void **)&a);
printf("*a = %d
", *a);
printf("线程1退出
");
#endif
pthread_join(thread2, NULL);
printf("线程2退出
");
return 0;
}
(6)pthread_detach( )
#include
int pthread_detach(pthread_t thread);
功能:主线程与子线程分离,子线程结束后,资源自动回收
参数:
thread:要分离的线程的id
返回值:
成功:0
失败:错误信息
#include
#include
#include
void *pthread_fun1(void *arg)
{
printf("子线程1:%#lx 正在运行...
", pthread_self());
//pthread_detach(pthread_self());
}
void *pthread_fun2(void *arg)
{
printf("子线程2:%#lx 正在运行...
", pthread_self());
//pthread_detach(pthread_self());
}
int main(int argc, const char *argv[])
{
printf("主控线程:%#lx 正在运行...
", pthread_self());
pthread_t thread1, thread2;
if(pthread_create(&thread1, NULL, pthread_fun1, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
if(pthread_create(&thread2, NULL, pthread_fun2, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
printf("thread1 = %#lx
", thread1);
printf("thread2 = %#lx
", thread2);
//将子线程设置为分离属性,也可以在子线程内部执行
pthread_detach(thread1);
pthread_detach(thread2);
//如果进程结束,则所有的线程都会退出
while(1)
{
printf("hello world
");
sleep(1);
}
return 0;
}
【10】线程的同步与互斥
(1)互斥、同步
互斥:多个任务不能同时进行,同一时间只能执行一个任务
同步:在互斥的基础上有顺序执行
(2)互斥锁
如果多个线程要对同一个共享资源进行操作,那么为了保证执行的正确性,需要使得线程之间互斥,使用
互斥锁。
互斥锁的机制是对共享资源的操作进行
上锁和解锁的过程,如果要操作共享资源,则线程需要先上锁,只要有一个线程已经上锁,那么其他线程需要阻塞等待,如果线程对共享资源操作完毕,则解锁,解锁有,其他线程就又可以上锁并操作。
函数
1 – pthread_mutex_init( )
#include
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
功能:初始化一个锁
参数:
mutex:指定的锁
attr:锁的属性,NULL表示默认
返回值:
成功:0
失败:非0
2 – pthread_mutex_destroy( )
#include
int pthread_mutex_destroy(pthread_mutex_t *mutex);
功能:销毁一个锁
参数:
mutex:指定的锁
返回值:
成功:0
失败:非0
3 – pthread_mutex_lock( )
#include
int pthread_mutex_lock(pthread_mutex_t *mutex);
功能:上锁
参数:
mutex:指定的锁
返回值:
成功:0
失败:非0
4 – pthread_mutex_unlock( )
#include
int pthread_mutex_unlock(pthread_mutex_t *mutex);
功能:解锁
参数:
mutex:指定的锁
返回值:
成功:0
失败:非0
#include
#include
#include
//互斥锁的使用
//第一步:创建互斥锁
pthread_mutex_t mutex;
int money = 10000;
void *pthread_fun1(void *arg)
{
//第三步:对共享资源的操作进行上锁
pthread_mutex_lock(&mutex);
int yu, get = 10000, shiji;
printf("线程1正在查询余额...
");
sleep(1);
yu = money;
printf("线程1正在取钱...
");
sleep(1);
if(get > yu)
{
shiji = 0;
}
else
{
shiji = get;
yu = yu - get;
money = yu;
}
printf("线程1:%#x, 想取%d, 实际取%d, 余额%d
",pthread_self(), get, shiji, yu);
//第四步:如果共享资源操作完毕,则解锁
pthread_mutex_unlock(&mutex);
pthread_exit(NULL);
}
void *pthread_fun2(void *arg)
{
//第三步:对共享资源的操作进行上锁
pthread_mutex_lock(&mutex);
int yu, get = 10000, shiji;
printf("线程2正在查询余额...
");
sleep(1);
yu = money;
printf("线程2正在取钱...
");
sleep(1);
if(get > yu)
{
shiji = 0;
}
else
{
shiji = get;
yu = yu - get;
money = yu;
}
printf("线程2:%#x, 想取%d, 实际取%d, 余额%d
",pthread_self(), get, shiji, yu);
//第四步:如果共享资源操作完毕,则解锁
pthread_mutex_unlock(&mutex);
pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
pthread_t thread1, thread2;
//第二步:初始化锁
pthread_mutex_init(&mutex, NULL);
if(pthread_create(&thread1, NULL, pthread_fun1, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
if(pthread_create(&thread2, NULL, pthread_fun2, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
//第五步:销毁锁
pthread_mutex_destroy(&mutex);
return 0;
}
(3)条件变量
一般条件变量和互斥锁一起使用。
当多个线程在互斥的基础上需要考虑执行顺序,可以使用条件变量让后执行的线程等待信号的产生,让先执行的线程执行完毕后,给后执行的
线程发送信号通知,后执行的线程接收到信号通知,才能继续执行。
函数:
1 – pthread_cond_init( )
#include
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
功能:初始化一个条件变量
参数:
cond:指定的条件变量
attr:条件变量的属性,NULL表示默认
返回值:
成功:0
失败:非0
2 – pthread_cond_destroy( )
#include
int pthread_cond_destroy(pthread_cond_t *cond);
功能:销毁一个条件变量
参数:
cond:指定的条件变量
返回值:
成功:0
失败:非0
3 – pthread_cond_wait( )
#include
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
功能:阻塞等待一个信号的产生
参数:
cond:指定的条件变量
mutex:指定的锁
返回值:
成功:0
失败:非0
4 – pthread_cond_signal( )
#include
int pthread_cond_signal(pthread_cond_t *cond);
功能:发送一个信号通知
参数:
cond:指定的条件变量
返回值:
成功:0
失败:非0
#include
#include
#include
int flags = 0;
pthread_mutex_t mutex;
//第一步:条件变量的使用
pthread_cond_t cond;
void *pthread_fun1(void *arg)
{
printf("线程1正在做蛋糕...
");
sleep(1);
printf("线程1蛋糕做好了...
");
pthread_mutex_lock(&mutex);
flags = 1;
pthread_mutex_unlock(&mutex);
//第四步:让先执行的线程执行完毕后发送信号通知
pthread_cond_signal(&cond);
pthread_exit(NULL);
}
void *pthread_fun2(void *arg)
{
pthread_mutex_lock(&mutex);
//第三步:后执行的线程阻塞等待信号的产生
//执行流程
// 1. 解锁
// 2. 上锁
// 3. 将要执行的线程加入到等待队列里面
// 4. 解锁
// 5. 上锁
pthread_cond_wait(&cond, &mutex);
if(flags == 1)
{
printf("线程2正在买蛋糕...
");
sleep(1);
printf("线程2蛋糕买完了...
");
flags = 0;
}
pthread_mutex_unlock(&mutex);
pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
pthread_t thread1, thread2;
pthread_mutex_init(&mutex, NULL);
//第二步:初始化条件变量
pthread_cond_init(&cond, NULL);
if(pthread_create(&thread1, NULL, pthread_fun1, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
if(pthread_create(&thread2, NULL, pthread_fun2, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
pthread_mutex_destroy(&mutex);
//第五步:销毁条件变量
pthread_cond_destroy(&cond);
return 0;
}
(4)信号量
信号量用于解决线程间同步问题,信号量又称之为PV操作,P操作是减操作,V操作是加操作,如果信号量的值为0,则没有办法执行P操作,此时P操作会阻塞,V操作没有限制。
一般初始时,将信号量的值设置为0,后执行的线程执行P操作阻塞等待,
先执行的线程执行完毕后,执行V操作使得信号量的值增大,则可以执行P操作。
函数:
1 – sem_init( )
#include
int sem_init(sem_t *sem, int pshared, unsigned int value);
功能:初始化一个信号量
参数:
sem:指定的信号量
pshared:是否在进程或者线程间共享
0 线程间共享
1 进程间 共享
value:信号量的初始值
返回值:
成功:0
失败:-1
2 – sem_destroy( )
#include
int sem_destroy(sem_t *sem);
功能:销毁一个信号量
参数:
sem:指定的信号量
返回值:
成功:0
失败:-1
3 – sem_wait( )
#include
int sem_wait(sem_t *sem);
功能:执行P操作,执行一次,信号量的值减1,如果信号量的值为0,则阻塞等待
参数:
sem:指定的信号量
返回值:
成功:0
失败:-1
4 – sem_post( )
#include
int sem_post(sem_t *sem);
功能:执行V操作,执行一次,信号量的值加1
参数:
sem:指定的信号量
返回值:
成功:0
失败:-1
#include
#include
#include
#include
//信号量的使用
//第一步:创建信号量
sem_t sem;
void *pthread_fun1(void *arg)
{
printf("线程1正在做蛋糕...
");
sleep(1);
printf("线程1蛋糕做好了...
");
//第四步:让先执行的线程执行完毕后执行V操作
sem_post(&sem);
pthread_exit(NULL);
}
void *pthread_fun2(void *arg)
{
//第三步:让后执行的线程执行P操作
sem_wait(&sem);
printf("线程2正在买蛋糕...
");
sleep(1);
printf("线程2蛋糕买完了...
");
pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
pthread_t thread1, thread2;
//第二步:初始化信号量
//初始时,将信号量的值设置为0
sem_init(&sem, 0, 0);
if(pthread_create(&thread1, NULL, pthread_fun1, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
if(pthread_create(&thread2, NULL, pthread_fun2, NULL) != 0)
{
perror("fail to pthread_create");
exit(1);
}
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
//第五步:销毁信号量
sem_destroy(&sem);
return 0;
}