系统编程小结

  • Post author:
  • Post category:其他


电脑上的进程线程可以在任务管理器中直接查看,具体查看方法自行百度。

一、进程

理论角度:

进程是资源分配的最小单位, 一个正在执行的程序就是进程

内存是隔离

作用: 实现多任务并发

缺点: 1>资源消耗大 2>进程间的通讯麻烦,要使用通讯及机制

优点: 1>稳定好

编程角度:

fork() exit() wait()

进程的状态

运行状态/就绪状态/停止状态/等待状态/僵尸状态

查看系统进程的信息 1> ps -aux/-ef 2> cd /proc 3> top

linux中直接输入指令 pstree -p,可以查看进程之间的关系(进程树)

  1. 进程控制


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):如果上面的宏为真, 过滤信号

  1. 系统函数

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;
}

二、线程

  1. 线程和进程的关系

  • 线程是属于进程的,线程运行在进程空间内,同一进程所产生的线程共享同一物理内存空间,当进程退出时该进程所产生的线程都会被强制退出并清除。一个进程至少需要一个线程作为它的指令执行体,进程管理着资源(比如cpu、内存、文件等等),而将线程分配到某个cpu上执行。

  • 如果说进程是资源管理的最小单位,那么线程是程序执行的最小单位。

  • 在操作系统设计上,从进程演化出线程,最主要的目的就是更好地支持多处理器,并且减小进程上下文切换的开销。

  • 线程是进程的一条执行路径,它包含独立的堆栈和CPU寄存器状态,每个线程共享其所附属的进程的所有的资源,包括打开的文件、内存页面、信号标识及动态分配的内存等等。

  1. 线程的资源

  • 线程自己基本上不拥有系统资源,只拥有少量在运行中必不可少的资源(如程序计数器、一组寄存器、栈、线程信号掩码、局部线程变量和线程私有数据),但是它可与同属一个进程的其他线程共享进程所拥有的全部资源(同一地址空间、通用的信号处理机制、数据与I/O)。

  • 进程在使用时占用了大量的内存空间,特别是进行进程间通信时一定要借助操作系统提供的通信机制,这使得进程有自身的弱点,而线程占用资源少,使用灵活,很多应用程序中都大量使用线程,而较少的使用多进程,但是,线程不能脱离进程而存在,另外,线程的层次关系,执行顺序并不明显,对于初学者大量使用线程会增加程序的复杂度。

  1. 线程相关函数

头文件:#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;
}

三、进程间通讯

  1. 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;
}
  1. 无名管道

一种基于内存的通信机制,用于在父进程和子进程之间传递数据。它是一种单向的通道,可以实现单向的通信。

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;
}
  1. 有名管道

是一种在进程间进行通信的方法,它可以在不同的进程之间创建一个命名的管道,以便它们可以通过该管道进行通信。与匿名管道不同,有名管道可以在进程间共享,因此可以用于不同计算机或不同用户之间的进程通信。

在 Linux 和 Unix 系统中,有名管道通常是通过创建一个文件来实现的,该文件用于在进程之间传递数据。在 Windows 系统中,有名管道则是使用一个命名对象来实现的,该对象用于在进程之间传递数据。

int mkfifo(const char *pathname, mode_t mode);

头文件:#include<sys/type.h>

#include<sys/stat.h>

参数说明:

*pathname:指定管道路径

mode:指定管道权限

返回值:成功返回0,失败返回-1

需要注意的是,当一个进程打开一个管道进行写操作时,如果没有其他进程打开同一管道进行读操作,写进程将被阻塞,直到有其他进程打开了该管道进行读操作为止。同样,当一个进程打开一个管道进行读操作时,如果没有其他进程打开同一管道进行写操作,读进程也将被阻塞,直到有其他进程打开了该管道进行写操作为止。因此,需要保证有名管道的读写操作是并发的,否则可能会导致死锁等问题。

  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;
}
  1. 消息队列

消息队列是一种进程间通信机制,它允许一个或多个进程向队列中发送消息,而另一个或多个进程则从队列中接收消息。消息队列是一个消息的列表,由内核维护,它可以存储消息的队列是基于 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);
  1. 共享内存

共享内存是指多个进程共享同一块物理内存,用于高效地进行进程间数据通信和共享数据。在共享内存中,每个进程都可以直接访问共享的内存区域,而无需进行数据的复制和传输,因此可以

实现较高的数据传输效率和实时性

。通常,共享内存适用于需要

频繁交换大量数据的进程间通信场景

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 进行操作。同时,也需要进行同步和互斥处理,以避免多个进程同时访问共享内存导致的数据不一致和冲突。

  1. 信号量集合

信号量是用来控制进程对共享资源的访问的一种机制。信号量可以用来实现进程同步、互斥和进程间通信等功能。而信号量集合是由多个信号量组成的集合,可以用来管理多个共享资源。

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;
}

四、线程间的通讯

  1. 互斥锁:

用于保护共享资源的独占访问,即同一时刻只有一个线程可以访问共享资源。

  • 定义: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;
}

  1. 自旋锁

自旋锁是一种特殊的互斥锁,它采用忙等待的方式来实现同步,即在获取锁时不会阻塞线程,而是不断地尝试获取锁。屏障用于同步多个线程的执行,它可以使多个线程在某个时刻达到同步,以便继续执行后续的操作。

  • 定义: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

来释放与之相关联的资源。这可以避免内存泄漏并提高系统资源的利用率。

  1. 读写锁

读写锁用于保护共享资源的读写操作,它允许多个线程同时进行读操作,但只允许一个线程进行写操作。

  • 定义: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);//释放锁
  1. 条件变量

条件变量用于线程间的同步,通常用于等待某个事件的发生。一般与互斥锁一起使用

  • 定义: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;
}



版权声明:本文为qq_65041077原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。