电脑上的进程线程可以在任务管理器中直接查看,具体查看方法自行百度。
一、进程
理论角度:
进程是资源分配的最小单位, 一个正在执行的程序就是进程
内存是隔离
作用: 实现多任务并发
缺点: 1>资源消耗大 2>进程间的通讯麻烦,要使用通讯及机制
优点: 1>稳定好
编程角度:
fork() exit() wait()
进程的状态
运行状态/就绪状态/停止状态/等待状态/僵尸状态
查看系统进程的信息 1> ps -aux/-ef 2> cd /proc 3> top
linux中直接输入指令 pstree -p,可以查看进程之间的关系(进程树)
-
进程控制
pid_t fork(void)
:
功能 :创建子进程, 在一个程序中一旦调用fork函数, 那么就会创建一个子进程, 父进程会将自己所有的资源拷贝一个给子进程,
所有的资源包含: 堆 栈 数据段 代码段 缓冲区 CPU的状态
注意事项:
1> 子进程从fork的下一条指令开始执行
2> 父子进程的执行顺序不确定
3> 在父进程中返回子进程的ID, 在子进程中返回0*kill 命令
kill -l:常看系统信号值
kill -9 进程号: 向指定进程发送9这个信号, 9这个信号会杀死进程,让进程强制退出!
exit()
: 进程退出函数
ps: 在main函数中, return和exit的作用是一样的
在其他子函数中, return表示本函数结束, exit仍然表示结束程序
孤儿进程:
产生原因:父进程结束或者被杀死,子进程还在运行
解决办法:本身无危害,
孤儿进程会被init 进程 (进程号为 1)所收养,并由 init 进程对它们完成状态收集工作
僵尸进程:
产生原因:子进程结束后 没有被 父进程回收(wait)
解决办法:等待父进程结束,或者杀死父进程,使僵尸进程变成孤儿进程,在被init进程收养。
wait()
: 回收子进程的资源
注意事项:
1、如果没有子进程, wait函数是一个非阻塞函数
2、如果有子进程, wait 是一个阻塞函数, 当有子进程退出时, wait会自动解除阻塞回收子进程的资源
3、wait 有一个参数, 是传出参数,返回回收进程的退出原因和返回值
如果对回收进程的退出原因和返回值不感兴趣,直接传NULL
如果感兴趣,传一个整型变量的地址过去
4、如何过滤回收进程的退出原因和返回值
WIFEXITED(wstatus):如果是调用exit 或者 main函数中的return退出,返回真
WEXITSTATUS(wstatus): 如果上面的宏为真, 过滤退出的返回值
WIFSIGNALED(wstatus): 如果回收的进程是被信号杀死, 该宏为真
WTERMSIG(wstatus):如果上面的宏为真, 过滤信号
-
系统函数
system()
int system(const char * command)
头文件:#include<stdio.h>
command:命令行
功能:执行shell(Linux/Unix系统) 命令。在Linux/Unix系统中,system函数会调用fork函数产生子进程,由子进程来执行command命令,命令执行完后随即返回原调用的进程。
#include <stdio.h>
#include <stdlib.h>
int m
{
printf("main...\n");
system("ls -l; pwd; touch file.txt");//等同于在操作界面直接输入ls -l; pwd; touch file.txt
printf("main over...\n");
return 0;
}
execl()
int execl(const char *pathname, const char *arg, …,(一般结尾加NULL));
头文件:
#
include <unistd.h>
*pathname: 要执行的文件的路径(推荐使用绝对路径)
*arg[0]、*arg[1]…: 执行该文件时传递的命令
功能:执行shell(Linux/Unix系统) 命令。在Linux/Unix系统中,system函数会调用fork函数产生子进程,由子进程来执行command命令,命令执行完后随即返回原调用的进程。
#include <stdio.h>
#include <unistd.h>
int main01()
{
pid_t pid;
printf("main....\n");
pid = fork();
if(pid==0)
{
execl("/bin/ls", "ls", "-l", NULL);//等同于在命令界面打出 ls -l
}
printf("main over....\n");
return 0;
}
int main()
{
printf("main...\n");
// execlp("ls", "ls", "-l", NULL)
char* argv[] = {"ls", "-l", NULL};//可以用指针数组保存arg[1]、arg[2]...
execv("/bin/ls", argv);
printf("main over,,,\n");
return 0;
}
二、线程
-
线程和进程的关系
-
线程是属于进程的,线程运行在进程空间内,同一进程所产生的线程共享同一物理内存空间,当进程退出时该进程所产生的线程都会被强制退出并清除。一个进程至少需要一个线程作为它的指令执行体,进程管理着资源(比如cpu、内存、文件等等),而将线程分配到某个cpu上执行。
-
如果说进程是资源管理的最小单位,那么线程是程序执行的最小单位。
-
在操作系统设计上,从进程演化出线程,最主要的目的就是更好地支持多处理器,并且减小进程上下文切换的开销。
-
线程是进程的一条执行路径,它包含独立的堆栈和CPU寄存器状态,每个线程共享其所附属的进程的所有的资源,包括打开的文件、内存页面、信号标识及动态分配的内存等等。

-
线程的资源
-
线程自己基本上不拥有系统资源,只拥有少量在运行中必不可少的资源(如程序计数器、一组寄存器、栈、线程信号掩码、局部线程变量和线程私有数据),但是它可与同属一个进程的其他线程共享进程所拥有的全部资源(同一地址空间、通用的信号处理机制、数据与I/O)。
-
进程在使用时占用了大量的内存空间,特别是进行进程间通信时一定要借助操作系统提供的通信机制,这使得进程有自身的弱点,而线程占用资源少,使用灵活,很多应用程序中都大量使用线程,而较少的使用多进程,但是,线程不能脱离进程而存在,另外,线程的层次关系,执行顺序并不明显,对于初学者大量使用线程会增加程序的复杂度。
-
线程相关函数
头文件:#include <pthread.h>
-
创建线程(pthread_create)
int pthread_create(pthread_t *restrict thread,const pthread_attr_t *restrict attr,
void *(*start_routine)(void*), void *restrict arg);
pthread_t: 线程id数据类型
thread:指向线程标识符的指针
attr:线程属性,一般为NULL
start_routine:线程的入口地址
arg: 给新线程传递参数
返回值: 成功返回0, 失败返回错误编号
ps:编译时如果编译失败,可以在尾部加 -lpthread 选项
gcc xx.c -o xx -lpthread
-
等待线程结束函数(pthread_join)
int pthread_join(pthread_t thread,void **value_ptr);
thread:指向线程标识符的指针
value_ptr:传出参数,返回回收线程的返回值
返回值: 成功返回0, 失败返回错误编号
1、如果指定的线程不存在,pthread_join函数是一个非阻塞函数
2、如果执行的线程存在并且没有结束, pthread_join函数是一个阻塞函数,当线程结束,pthread_join立马解除阻塞回收线程资源。
-
退出线程(pthread_exit)
void pthread_exit(void *value_ptr);
thread:指向线程标识符的指针
value_ptr:传出参数,返回回收线程的返回值
返回值: 成功返回0, 失败返回错误编号
区分pthread_exit和return:
在线程入口函数中, pthread_exit和return的效果是一样的
但在子函数中, return表示本函数结束, 而pthread_exit仍然表示结束当前线程
多线程中慎用exit函数
-
终止线程(pthread_cancle)
在线程外面终止线程
int pthread_cancel(pthread_t thread);
thread:指向线程标识符的指针
返回值: 成功返回0, 失败返回错误编号
-
分离线程(pthread_detach)
int pthread_detach(pthread_t thread);
thread:指向线程标识符的指针
返回值: 成功返回0, 失败返回错误编号
功能从状态上实现线程分离,注意不是指该线程独自占用地址空间。
线程分离状态:
指定该状态,线程主动与主控线程断开关系。线程结束后(不会产生僵尸线程),其退出状态不由其他线程获取,而直接自己自动释放(自己清理掉PCB的残留资源)。网络、
多线程
服务器常用。
PS:进程可以通过监听信号进行分离
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
struct student
{
int num;
char name[32];
};
//不要返回局部变量的地址!!!!!
void* pthread_task(void* argv)
{
//static int a = 1;
struct student *p = NULL;
p = (struct student*)malloc(sizeof(struct student));
assert(p!=NULL);
p->num = 1001;
strcpy(p->name, "lisi");
for(int i=1; i<=5; i++)
{
printf("new pthread....\n");
sleep(1);
}
pthread_exit(p);//推出该线程,返回P的地址
}
int main()
{
printf("main...\n");
pthread_t pth;//创建线程标识符
if(0 != pthread_create(&pth, NULL, pthread_task, NULL))//创建线程在pthread_create函数处
{
perror("pthread_create");
return -1;
}
//pthread_detach(pth);//分离线程,这里会影响其它组件,注释掉了
struct student* p = NULL;
printf("join...\n");
pthread_join(pth, (void**)&p);//阻塞监听线程是否结束,结束后回收,并接收线程的返回值,如果没有线程则不阻塞。
printf("join over...p=%p, %d-%s\n", p, p->num, p->name);
return 0;
}
三、进程间通讯
-
signal
-
signal
给信号注册处理函数
typedef void (*sighandler_t)(int);//信号处理函数
sighandler_t signal(int signum, sighandler_t handler);//监听信号
头文件:#include<signal.h>
参数说明:
signum: 信号值
handle: 注册处理方法
<1> 捕捉:传自定义函数入口地址
<2> 忽略: SIG_IGN
<3> 执行默认操作: SIG_DFL
返回值:返回之前的处理方法
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void hangle(int sig)
{
printf("recv sig: %d\n", sig);
}
int main()
{
//signal(SIGUSR2, hangle);
//signal(SIGINT, hangle);//
signal(SIGSEGV, hangle);//捕捉段错误
char *p = NULL;
*p = 10;
while(1)
{
printf(".");
fflush(stdout);
sleep(1);
}
return 0;
}

-
kill
用于向指定的进程发送信号
int kill(pid_t pid, int sig);
头文件:#include<signal.h>
参数说明:
pid: 进程的进程ID
sig:发送的信号编号
返回值:成功返回0,失败返回-1
sig参数一般设置为预定义的信号宏(例如
SIGKILL
表示强制结束进程,
SIGTERM
表示终止进程等)需要注意的是,只有具有足够权限的进程才能向另一个进程发送信号。
-
alarm
用于在指定时间后向进程发送一个
SIGALRM
信号,以便触发进程对该信号的处理函数
unsigned int alarm(unsigned int seconds);
头文件:#include<unistd.h>
参数说明:
seconds:定时器的时长(单位为秒(s))
返回值:如果函数成功,则返回未处理的剩余时间;如果之前已经设置了定时器,则返回该定时器的剩余时间;如果没有设置定时器,则返回0
如果
seconds
参数为0,则表示取消之前设置的定时器。
一般和signal函数一起使用。
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void handler(int sig) {
printf("Caught signal %d\n", sig);
exit(0);
}
int main() {
signal(SIGALRM, handler);//监听alarm的信号
alarm(5);//5s后发送信号,触发信号处理函数
while(1) {
printf("Sleeping...\n");
sleep(1);
}
}
-
setitimer
用于设置一个定时器,并在定时器到期时发送一个信号给进程。它可以用于实现一些需要定时执行的操作,比如轮询某个设备、定时发送心跳包等。
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);
头文件:#include<sys/time.h>
which:表示要设置的定时器类型。
ITIMER_REAL
:基于真实时间,以系统的实时时钟计时。定时器到期时,将向进程发送一个
SIGALRM
信号。
ITIMER_VIRTUAL
:基于进程虚拟时间,以进程的CPU时间计时。定时器到期时,将向进程发送一个
SIGVTALRM
信号。
ITIMER_PROF
:基于进程虚拟时间和系统CPU时间,以进程和系统CPU时间计时。定时器到期时,将向进程发送一个
SIGPROF
信号。
*new_value:指向
struct itimerval
结构体的指针,用于设置定时器的初始值和间隔值
*old_value:参数是一个指向
struct itimerval
结构体的指针,用于存储原先定时器的值(如果有的话)。
struct itimerval {
struct timeval it_interval; // 定时器间隔
struct timeval it_value; // 定时器初始值
};
struct timeval {
time_t tv_sec; //秒数
suseconds_t tv_usec; //微妙数
};
返回值:成功返回0,失败返回-1,并设置错误码。
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <unistd.h>
#include <signal.h>
void timer_handler(int sig) {
printf("Timer expired\n");
}
int main() {
struct sigaction sa;
struct itimerval timer;//定时器
// 安装定时器处理函数
sa.sa_handler = timer_handler;
sigemptyset(&sa.sa_mask);//初始化sa.sa_mask
sa.sa_flags = 0;
if (sigaction(SIGALRM, &sa, NULL) == -1) {
perror("sigaction");
exit(1);
}
// 设置定时器
timer.it_value.tv_sec = 1;
timer.it_value.tv_usec = 0;
timer.it_interval.tv_sec = 1;
timer.it_interval.tv_usec = 0;
if (setitimer(ITIMER_REAL,&timer, NULL) == -1)
{
perror("setitimer");
exit(1);
}
while(1) {
// 等待信号
pause();
}
return 0;
}
-
无名管道
一种基于内存的通信机制,用于在父进程和子进程之间传递数据。它是一种单向的通道,可以实现单向的通信。
int pipe(int pipefd[2]);
头文件:#include<unistd.h>
参数说明:pipefd[2]: 用于返回两个文件描述符
pipefd[0]表示管道的读取端;
pipefd[1]表示管道的写入端;
无名管道是
一种半双工的通信机制
,只能实现
单向的通信
,
需要配合其他机制才能实现双向通信
。常见的用法是在
父子进程之间
传递数据。
父进程
使用管道的
写入端
写入数据,
子进程
使用管道的
读取端
读取数据,从而实现父子进程之间的通信。
无名管道
只适用于父子进程之间的通信
,因为它是基于进程间共享内存实现的。如果要在其他进程之间进行通信,可以使用命名管道或其他进程间通信机制,如共享内存、消息队列等。
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <stdlib.h>
#include <assert.h>
//表示包头的基本信息
struct pack
{
unsigned char ver; //版本号
unsigned char type; // 类型
unsigned int len; // 打包数据的长度
char data[0];
};
void hangle(int sig)
{
printf("recv sig: %d\n", sig);
}
int main()
{
//1、创建管道
int fd[2], ret;
pid_t pid;
char buff[1024];
ret = pipe(fd);
if(ret<0)
{
perror("pipe");
return -1;
}
//2、分裂进程
pid = fork();
if(pid>0) //父进程: 写数据
{
signal(SIGPIPE, hangle);
close(fd[0]);
struct pack* p= NULL;
while(1)
{
printf("请输入数据:");
scanf("%s", buff);
while(getchar()!='\n');
//封包
//<1>申请一块内存空间:包头+数据大小
p = malloc(sizeof(struct pack)+strlen(buff));
assert(p!=NULL);
//<2> 填写包头信息, 再将打包的数据加载到打包的内存中
p->ver = 0;
p->type = 0;
p->len = strlen(buff);
strcpy(p->data, buff);
ret=write(fd[1], p, sizeof(struct pack)+strlen(buff));
if(ret<0)
{
perror("write");
break;
}
}
close(fd[1]);
}
else if(0 == pid) //子进程: 读数据
{
close(fd[1]);
struct pack pk;
sleep(10);
while(1)
{
//解包
//<1>先读包头
printf("read...\n");
ret = read(fd[0], &pk, sizeof(struct pack));
printf("read over...ret=%d\n", ret);
if(ret == 0) // 管道断裂
{
printf("pipe broken...\n");
break;
}
if(ret<0)
{
perror("read");
break;
}
//<2> 根据包头的长度去读取数据
ret = read(fd[0], buff, pk.len);
if(ret == 0) // 管道断裂
{
printf("pipe broken...\n");
break;
}
if(ret<0)
{
perror("read");
break;
}
buff[ret] = '\0';
printf("buff: %s\n", buff);
}
close(fd[0]);
}
return 0;
}
-
有名管道
是一种在进程间进行通信的方法,它可以在不同的进程之间创建一个命名的管道,以便它们可以通过该管道进行通信。与匿名管道不同,有名管道可以在进程间共享,因此可以用于不同计算机或不同用户之间的进程通信。
在 Linux 和 Unix 系统中,有名管道通常是通过创建一个文件来实现的,该文件用于在进程之间传递数据。在 Windows 系统中,有名管道则是使用一个命名对象来实现的,该对象用于在进程之间传递数据。
int mkfifo(const char *pathname, mode_t mode);
头文件:#include<sys/type.h>
#include<sys/stat.h>
参数说明:
*pathname:指定管道路径
mode:指定管道权限
返回值:成功返回0,失败返回-1
需要注意的是,当一个进程打开一个管道进行写操作时,如果没有其他进程打开同一管道进行读操作,写进程将被阻塞,直到有其他进程打开了该管道进行读操作为止。同样,当一个进程打开一个管道进行读操作时,如果没有其他进程打开同一管道进行写操作,读进程也将被阻塞,直到有其他进程打开了该管道进行写操作为止。因此,需要保证有名管道的读写操作是并发的,否则可能会导致死锁等问题。
-
文件锁
一种用于控制对文件访问的机制,它可以保证同一时间只有一个进程可以对同一个文件进行写操作,避免多个进程同时修改同一文件而造成的数据不一致的问题。文件锁可以通过使用 fcntl 函数在 Unix/Linux 系统上进行设置和操作。
int fcntl(int fd, int cmd, ... /* struct flock *flockptr */);
头文件:#include<fcntl.h>
参数说明:
fd: 文件描述符
cmd: 命令
F_GETLK:获取文件锁当前的状态。
F_SETLK:设置文件锁。
F_SETLKW:这是F_SETLK的阻塞版本
*flockptr: 传入参数, 设置文件锁的基本信息
struct flock {
...
short l_type; /* 锁的类型: F_RDLCK,
F_WRLCK, F_UNLCK */
short l_whence; /* 设置参考点:
SEEK_SET, SEEK_CUR, SEEK_END */
off_t l_start; /* 以参考点为基准,设置加锁的开始位置 */
off_t l_len; /* 设置加锁的长度*/
...
};
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
int fd, ret;
fd = open("./data.txt", O_CREAT|O_WRONLY|O_TRUNC, 0777);//以写的方式打开一个文件
if(fd<0)
{
perror("open");
return -1;
}
struct flock fk;//设置文件锁属性
fk.l_type = F_WRLCK;//读锁
fk.l_whence = SEEK_SET;//指向文档起点
fk.l_start = 0;
fk.l_len = 100;
fcntl(fd, F_SETLKW, &fk);//加锁(阻塞)
write(fd, "hello", 5);//写入数据
sleep(20);
fk.l_type = F_UNLCK;//解锁
fcntl(fd, F_SETLKW, &fk);
close(fd);
return 0;
}
-
消息队列
消息队列是一种进程间通信机制,它允许一个或多个进程向队列中发送消息,而另一个或多个进程则从队列中接收消息。消息队列是一个消息的列表,由内核维护,它可以存储消息的队列是基于 FIFO(先进先出)原则的,即先进入队列的消息先被接收。
int msgget(key_t key, int msgflg);//创建或获取一个消息队列的标识符,
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);//指定的消息队列发送一条消息
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);//从指定的消息队列接收一条消息
头文件:#include<sys/msg.h>
参数说明:
key:消息队列的键值;(在Linux系统中,使用ftok()函数可以根据一个给定的文件名和一个整数生成一个键值)
key_t ftok(const char *pathname, int proj_id);//使用pathname(用于生成键值的文件路径)的i-node号和项目ID(proj_id)共同生成一个键值,该键值和文件路径名成一一对应关系,借此保证键值的唯一性。(和文件本身没关系)
同一个操作系统中,相同的键值可能会被多个不同的消息队列使用,因此在创建或打开一个消息队列时,需要确保使用唯一的键值。
msgfig:参数指定了创建新队列时的权限和选项
int msg_id;
msg_id = msgget(0x100, IPC_CREAT|0777);
msqid:消息队列的标识符;
msgp:指向包含消息数据的缓冲区
//参照这个结构体定义
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[1]; /* message data */
};
msgsz:消息数据的长度
msgflg:指定了发送消息时的选项
int msg_id;
struct msgbuf mb = {2, "huaqing zhenbang!"};
msgsnd(msg_id, &mb, strlen(mb.mtext), 0);
msgtyp:指定了需要接收的消息类型
msgflg:设置阻塞标志,默认0表示阻塞
struct msgbuf mb;
msgrcv(msg_id, &mb, 1024, 2, 0);
-
共享内存
共享内存是指多个进程共享同一块物理内存,用于高效地进行进程间数据通信和共享数据。在共享内存中,每个进程都可以直接访问共享的内存区域,而无需进行数据的复制和传输,因此可以
实现较高的数据传输效率和实时性
。通常,共享内存适用于需要
频繁交换大量数据的进程间通信场景
。
int shmget(key_t key, size_t size, int shmflg);//创建共享内存,成功返回共享内存标志符,失败返回-1
void *shmat(int shmid, const void *shmaddr, int shmflg);//用来将共享内存区域映射到当前进程的地址空间的函数。成功返回一个指向共享内存区域起始地址的指针,失败啧返回(void *)-1
头文件:#include<sys/shm.h>
参数说明:
key:键值
size:内存大小
shmflg:设置标志
*shmaddr:指定共享内存映射到当前进程地址,如果为NULL,系统自动分配
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/shm.h>
#define SHM_SIZE 1024
int main() {
key_t key = ftok("/tmp/shmfile", 1);//生成唯一键值
int shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0666);//创建共享内存
if (shmid == -1) {
perror("shmget");
exit(1);
}
char *data = shmat(shmid, NULL, 0);//将共享内存的空间映射到当前进程地址空间的函数
if (data == (char *)(-1)) {
perror("shmat");
exit(1);
}
strcpy(data, "Hello, shared memory!");
printf("Data written to shared memory: %s\n", data);
//将共享内存从当前进程中分离,完成共享内存的使用
if (shmdt(data) == -1) {
perror("shmdt");
exit(1);
}
return 0;
}
共享内存的使用需要谨慎,一旦出现内存泄漏或其他问题,可能会导致整个系统崩溃。因此,在使用共享内存时需要特别小心,并严格按照操作系统提供的 API 进行操作。同时,也需要进行同步和互斥处理,以避免多个进程同时访问共享内存导致的数据不一致和冲突。
-
信号量集合
信号量是用来控制进程对共享资源的访问的一种机制。信号量可以用来实现进程同步、互斥和进程间通信等功能。而信号量集合是由多个信号量组成的集合,可以用来管理多个共享资源。
int semget(key_t key, int nsems, int semflg);//创建信号量集合,成功返回信号量集合标识符,失败返回-1
int semctl(int semid, int semnum, int cmd, ...);//控制信号量集合
int semop(int semid, struct sembuf *sops, size_t nsops);//操作信号量集合
头文件:semget–#include<sys/type.h> #include<sys/ipc.h> #include<sys/sem.h>
semctl–#include<sys/sem.h>
semop–#include<sys/sem.h>
参数说明:
key:键值
nsems:信号量集合中包含的信号量数目
semflg: 用于指定信号量集合的权限和操作方式,可以设置为0
semid: 信号量集合的标识符
semnum:指定要操作的信号量的编号
cmd: 是指定要执行的命令

*sops: 指向要执行的操作的结构体数组的指针
struct sembuf:
unsigned short sem_num; /* 操作信号量的下标 */
short sem_op; /* p: + v: - */
short sem_flg; /* 设置阻塞标志: SEM_UNDO */
nsops: 要执行的行数
semop()
函数可以执行多个操作,每个操作由一个
sembuf
结构体表示,包括信号量编号、操作类型和操作数等信息。操作类型可以为 P(申请资源)或 V(释放资源),操作数可以为 1 或 -1,分别表示增加或减少信号量的值。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
};
int main() {
key_t key = ftok("/tmp/semfile", 1);
int semid = semget(key, 2, IPC_CREAT | 0666);
if (semid == -1) {
perror("semget");
exit(1);
}
union semun arg;
arg.val = 1;
if (semctl(semid, 0, SETVAL, arg) == -1) {
perror("semctl");
exit(1);
}
arg.val = 0;
if (semctl(semid, 1, SETVAL, arg) == -1) {
perror("semctl");
exit(1);
}
struct sembuf sops[2];
sops[0].sem_num = 0;
sops[0].sem_op = -1;
sops[0].sem_flg = SEM_UNDO;
sops[1].sem_num = 1;
sops[1].sem_op = 1;
sops[1].sem_flg = SEM_UNDO;
if (fork() == 0) {
/* Child process */
printf("Child: before semop()\n");
if (semop(semid, sops, 2) == -1) {
perror("semop");
exit(1);
}
printf("Child: after semop()\n");
} else {
/* Parent process */
printf("Parent: before semop()\n");
if (semop(semid, sops, 2) == -1) {
perror("semop");
exit(1);
}
printf("Parent: after semop()\n");
}
if (semctl(semid, 0, IPC_RMID, arg) == -1) {
perror("semctl");
exit(1);
}
return 0;
}
四、线程间的通讯
-
互斥锁:
用于保护共享资源的独占访问,即同一时刻只有一个线程可以访问共享资源。
-
定义:pthread_mutex_t mutex;
-
初始化:
动态方法
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
静态方式:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
ps: 定义互斥锁和初始化要在创建线程之前完成
-
加锁:
int pthread_mutex_lock(pthread_mutex_t *mutex);
-
解锁:
int pthread_mutex_unlock(pthread_mutex_t *mutex);
加锁原则:
1、尽量只给对公共资源访问的几行代码加锁,范围越小越好
2、加锁的代码中尽量不要出现死循环或者睡眠函数
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
char buff[1024];
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; //manage buff
void* pthread1(void* argv)
{
while(1)
{
printf("lock...\n");
//判断锁的状态, 如果是一个锁定的状态,lock函数阻塞等待, 如果是一个非锁定状态,加锁,解除阻塞,访问公共资源
pthread_mutex_lock(&mutex);
printf("lock over...\n");
strcpy(buff, "hello world");
printf("buff: %s\n",buff);
pthread_mutex_unlock(&mutex);
sleep(1);
}
return NULL;
}
int main()
{
pthread_t pth;
if(0!=pthread_create(&pth, NULL, pthread1, NULL))
{
perror("pthread_create");
return -1;
}
while(1)
{
pthread_mutex_lock(&mutex);
strcpy(buff, "nihao zhangsan!");
printf("buff: %s\n",buff);
pthread_mutex_unlock(&mutex);
}
return 0;
}
-
自旋锁
自旋锁是一种特殊的互斥锁,它采用忙等待的方式来实现同步,即在获取锁时不会阻塞线程,而是不断地尝试获取锁。屏障用于同步多个线程的执行,它可以使多个线程在某个时刻达到同步,以便继续执行后续的操作。
-
定义:pthread_spinlock_t spin;
-
初始化:
int pthread_spin_init(pthread_spinlock_t *, int);
-
加锁:
int pthread_spin_lock(pthread_spinlock_t *);
int pthread_spin_trylock(pthread_spinlock_t *);//用于尝试获取自旋锁。如果锁当前未被占用,则函数将获取锁并返回 0;否则函数将立即返回并返回一个非零值表示锁已经被占用。与 pthread_spin_lock 不同的是,pthread_spin_trylock 不会一直等待锁的释放,而是立即返回。
pthread_spin_trylock
可以用于在不阻塞线程的情况下尝试获取自旋锁。它适用于需要在某个时间段内尝试获取锁,并且如果获取不到就继续做其他事情的情况。如果需要在获取锁时阻塞线程,则应该使用
pthread_spin_lock
。
-
解锁:
int pthread_spin_unlock(pthread_spinlock_t *);
-
删除自旋锁并释放资源
int pthread_spin_destroy(pthread_spinlock_t *);//用于销毁一个已经创建的自旋锁,释放与之相关联的资源,并将锁的状态设置为未初始化。
销毁一个自旋锁的过程中,需要确保没有任何线程在使用该自旋锁。如果在调
pthread_spin_destroy
时仍然有线程在使用自旋锁,则行为是未定义的。
当不再需要一个自旋锁时,应该调用
pthread_spin_destroy
来释放与之相关联的资源。这可以避免内存泄漏并提高系统资源的利用率。
-
读写锁
读写锁用于保护共享资源的读写操作,它允许多个线程同时进行读操作,但只允许一个线程进行写操作。
-
定义:pthread_rwlock_t rwlock;
-
初始化
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
*restrict rwlock:初始化的读写锁变量的指针
*restrict attr: 读写锁属性对象的指针
-
销毁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);//读锁
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);//写锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);//释放锁
-
条件变量
条件变量用于线程间的同步,通常用于等待某个事件的发生。一般与互斥锁一起使用
-
定义:pthread_cond_t cond;
-
初始化
pthread_cond_init(&cond, NULL);
-
判断是否有资源
while(没有有资源) //使用while循环不断地检查条件变量,如果条件不满足,则调用pthread_cond_wait函数将当前线程挂起,并释放互斥锁,直到条件满足时,线程才会被唤醒并重新获取互斥锁。
{
//1>在条件变量上阻塞等待, 在等待之前会释放互斥锁
//2>如果被唤醒,会申请加锁
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
}
-
唤醒等待进程
pthread_cond_signal((pthread_cond_t *restrict cond)//唤醒等待队列中的一个进程
pthread_cond_broadcast((pthread_cond_t *restrict cond)//唤醒等待队列中的全部进程
-
销毁
pthread_cond_destroy(&cond);
在使用条件变量时,需要注意避免死锁等问题。同时,也需要注意条件变量的正确使用,以确保线程安全和正确性。
/*消费者线程实现步骤
(1) 加锁
(2) 判断是否有资源
(3)解锁
生产者线程实现步骤
(1) 加锁
(2) 处理公共资源
(3) 解锁
(4) 发送信号,唤醒所有阻塞在条件变量上的线程*/
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
struct node
{
int data;
struct node*next;
};
//定义互斥锁和条件变量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
struct node *head = NULL;
//生产者
void* product(void* argv)
{
struct node *pnew = NULL;
sleep(20);
while(1)
{
pnew = (struct node*)malloc(sizeof(struct node));
assert(pnew!=NULL);
pnew->data = rand()%100;
pnew->next = NULL;
printf("-----product: %d....\n", pnew->data);
pthread_mutex_lock(&mutex);//加互斥锁
//使用资源
pnew->next = head;
head = pnew;
//解锁
pthread_mutex_unlock(&mutex);
//发送信号,唤醒阻塞在条件变量上的线程
pthread_cond_signal(&cond);
sleep(rand()%3);
}
return NULL;
}
//消费者
void* consumer(void *argv)
{
struct node *pdel = NULL;
while(1)
{
//加锁
pthread_mutex_lock(&mutex);
while(head == NULL)//判断是否有资源
{
printf("wait...\n");
pthread_cond_wait(&cond, &mutex);//将线程挂起,等待资源
printf("wait over...\n");
}
pdel = head;
printf("-----consumer:%d------\n", pdel->data);
head = head->next;
free(pdel);
pthread_mutex_unlock(&mutex);//解锁
sleep(rand()%3);
}
return NULL;
}
int main()
{
pthread_t pth1, pth2;
if(0 != pthread_create(&pth1, NULL, product, NULL))
{
perror("pthread_create");
return -1;
}
if(0 != pthread_create(&pth2, NULL, consumer, NULL))
{
perror("pthread_create");
return -1;
}
pthread_join(pth1, NULL);
pthread_join(pth2, NULL);
return 0;
}