目录
一,程序,进程和线程的概念
1,进程和程序的区别
2,Linux环境下的进程
3,进程和线程
二,进程产生的方式
1,进程号
2,进程复制fork()
3,system()方式
4,进程执行exec()函数系列
三,进程间通信和同步
1,半双工管道
2,命名管道
3,消息队列
4,消息队列的例子
5,信号量
6,共享内存
7,信号
四,Linux下的线程
1,多线程编程
2,Linux下线程创建函数pthread_create()
3,线程的结束函数pthread_join()和pthread_exit()
4,线程的属性
5,线程间的互斥
6,线程中使用信号量
1.1 进程和程序概念最大不同之处
Linux的进程操作方式主要有产生进程,终止进程,并且进程之间存在数据和控制的交互,即进程间通信和同步
2.1 进程的产生过程
进程的产生有多种方式,其基本过程是一致的。
2.2 进程的终止方式
有5种方式使进程终止
进程在终止的时候,系统会释放进程所拥有的资源,例如内存,文件符和内核结构
2.3 进程之间的通信
进程间通信方式有多种,其中管道,共享内存和消息队列是最常用的方式
2.4 进程之间的同步
Linux下进程的同步方式主要有消息队列,信号量等
1. 进程和线程的主要区别和联系
每个进程在初始化的时候,系统都分配了一个ID号,用于标识该进程。描述进程的ID号通常叫做PID
1.1 getpid(),getppid()函数介绍
pid_t getpid(void) :返回当前进程的ID号
pid_t getppid(void):返回当前进程的父进程的ID号
1.2 getpid()例子:获取当前程序的PID和父进程的PID
#include
#include
#include
int main()
{
pid_t pid, ppid;
pid = getpid(); //获取当前进程ID
ppid = getppid(); //获取父进程ID
printf("pid = %d\n", pid);
printf("ppid = %d\n", ppid);
return 0;
}
产生进程的方式比较多,fork()是其中的一种方式。fork()函数以父进程为蓝本复制一个进程,其ID号和父进程ID号不同。在Linxu环境下,fork()是以写复制实现的,只有内存等与父进程不同时,其他与父进程共享,只有在父进程或者子进程进行了修改后,才重新生成一份。
2.1 fork()函数介绍
pid_t fork(void)
fork()的特点是执行一次,返回两次。在父进程和子进程中返回的是不同的值,父进程中返回的是子进程的ID号,子进程中返回0。
2.2 fork()函数例子
在调用fork()函数之后,判断fork()函数的返回值:如果为-1,打印失败信息;如果为0,打印子进程信息;如果大于0,打印父进程信息。
#include
#include
#include
int main()
{
pid_t pid;
pid = fork(); //分叉进程
if (-1 == pid)
{
printf("进程创建失败!\n");
return -1;
}
else if (pid == 0)
{
printf("子进程,fork返回值: %d, ID:%d, 父进程ID:%d\n", pid, getpid(), getppid());
}
else
{
printf("父进程,fork返回值: %d, ID:%d, 父进程ID:%d\n", pid, getpid(), getppid());
}
return 0;
}
父进程,fork返回值: 51240, ID:51239, 父进程ID:25423 子进程,fork返回值: 0, ID:51240, 父进程ID:51239
fork出来的子进程的父进程的ID号是执行fork()函数的进程的ID号。
system()函数调用shell的外部命令在当前进程中开始另一个进程
3.1 system()函数介绍
#include
int system(const char *command);
执行system()函数时,会调用fork(),execve(),waitpid()等函数,其中任意一个调用失败,将导致system()函数调用失败。
system()函数返回值
exec()族函数会用新进程代替原有的进程,系统会从新的进程运行,新进程的PID值会与原来进程的PID值相同。
4.1 exec()族函数共有6个
#include
extern char **environ;
int execl(const char *path, const char *arg, ... );
int execlp(const char *file, const char *arg, ... );
int execle(const char *path, const char *arg, ... , char * const envp[] );
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *file, char *const argv[], char *const envp[]);
上述6个函数中,只有execve()函数是真正意义上的系统调用,其他5个函数都是在此基础上经过包装的库函数。上述的exec()函数族的作用是,在当前系统的可执行路径中根据指定的文件名来找到合适的可执行文件名,并用它来取代调用进程的内容,即在原来的进程内部运行一个可执行文件。上述的可执行文件即可以是二进制的文件,也可以是可执行的脚本文件。
与fork()函数不同,exec()函数族的函数执行成功后不会返回,这是因为执行的新程序已经占用了当前进程的空间和资源,这些资源包括代码段,数据段和堆栈等,它们都已经被新的内容取代,而进程的ID等标识性的信息任然是原来的东西,即exec()函数族在原来进程的壳上运行了自己的程序,只有程序调用失败了,系统才会返回-1.
如果在fork()系统调用之后进行exec()系统调用,系统就不会进行系统复制,而是直接使用exec()指定参数来覆盖原有的进程。上述的方法在Linux系统上叫做“写时复制”,即只有在造成系统的内容发生更改的时候才进行进程的真正更新。
4.2 execve()函数的例子
先打印调用进程的进程号,然后调用execve()函数,这个函数调用可执行文件“bin/ls”列出当前目录下的文件
#include
#include
#define FILELENGTH 80
int main(void)
{
char *args[] = {"/bin/ls", NULL};
printf("系统分配的进程号是%d\n", getpid());
if (execve("/bin/ls", args, NULL) < 0)
{
printf("创建进程出错\n");
}
return 0;
}
管道是一种把两个进程之间的标准输入和标准输出连接起来的机制。
1.1 基本概念
在shell中管道用"|"表示,ls -l|grep *.c 把ls -l的输出当做grep *.c的输入,管道在前一个进程中建立输入通道,在后一个进 程中建立输出通道。
进程创建管道,每次创建两个文件描述符来操作管道,其中一个对管道进行写操作,另一个对管道进行读操作
1.2 pipe()函数介绍
int pipe(int pipefd[2])
管道的操作是阻塞的。
1.3 pipe()函数例子
在子进程中向管道写入数据,在父进程中读取数据
#include
#include
#include
int main(void)
{
int result = -1;
pid_t pid;
int fd[2];
int nbyte;
char string[] = "你好,管道";
char readbuff[80];
//文件描述符1用于写,文件描述符0用于读
int *write_fd = &fd[1];
int *read_fd = &fd[0];
result = pipe(fd);
if (-1 == result)
{
printf("建立管道失败\n");
return -1;
}
pid = fork();
if (-1 == pid)
{
printf("fork 进程失败\n");
return -1;
}
if (pid == 0) //子进程
{
close(*read_fd); //关闭读管道
result = write(*write_fd, string, strlen(string));
return 0;
}
else //父进程
{
close(*write_fd); //关闭写管道
nbyte = read(*read_fd, readbuff, sizeof(readbuff));
printf("接收到%d数据,内容为%s\n", nbyte, readbuff);
}
return 0;
}
不同的进程可以通过命名管道共享数据
2.1 创建FIFO
2.2 FIFO操作
对命名管道来说,IO操作与普通的管道IO操作基本上是一样的,二者之间存在着一个主要的区别。在FIFO中,必须使用一个open()函数来显示的建立连接到管道的通道。一般来说FIFO总是处于阻塞状态。也就是说,如果命名管道FIFO打开时设置了读权限,则读进程将一直阻塞,一直到其他进程打开该FIFO并且向管道中写入数据。这个阻塞动作反过来也是成立的,如果一个进程打开一个管道写入数据,当没有进程从管道中读取数据的时候,写管道的操作也是阻塞的,知道已经写入的数据被读出后,才能进行写入操作。如果不希望在进行命名管道操作的时候发生阻塞,可以在open()调用中使用O_NONBLOCK标志,以关闭默认的阻塞操作。
消息队列是内核地址空间中的内部链表,通过Linux内核在各个进程之间传递内容。消息顺序地发送到消息队列中,并以几种不同地方式从队列中获取,每个消息队列可以用IPC标识符唯一地进行标识。
3.1 消息缓冲区结构
常用的结构是msgbuf结构
struct msgbuf {
long mtype;
char mtext[1];
};
3.2 结构msgid_ds
struct msqid_ds {
struct ipc_perm msg_perm;
struct msg *msg_first; /* first message on queue,unused */
struct msg *msg_last; /* last message in queue,unused */
__kernel_time_t msg_stime; /* last msgsnd time */
__kernel_time_t msg_rtime; /* last msgrcv time */
__kernel_time_t msg_ctime; /* last change time */
unsigned long msg_lcbytes; /* Reuse junk fields for 32 bit */
unsigned long msg_lqbytes; /* ditto */
unsigned short msg_cbytes; /* current number of bytes on queue */
unsigned short msg_qnum; /* number of messages in queue */
unsigned short msg_qbytes; /* max number of bytes on queue */
__kernel_ipc_pid_t msg_lspid; /* pid of last msgsnd */
__kernel_ipc_pid_t msg_lrpid; /* last receive pid */
};
3.3 结构ipc_perm
struct ipc_perm
{
__kernel_key_t key; //用于区分消息队列
__kernel_uid_t uid; //消息队列用户的ID号
__kernel_gid_t gid; //消息队列用户组的ID号
__kernel_uid_t cuid; //消息队列创建者的ID号
__kernel_gid_t cgid; //消息队列创建者的组ID号
__kernel_mode_t mode; //权限
unsigned short seq; //序列号
};
3.4. 键值构建ftok()函数
ftok()函数将路径名和项目的标识符转变为一个系统V的IPC键值
key_t ftok(const char *pathname, int proj_id)
3.5 获得消息msgget()函数
创建一个新的消息队列,或者访问一个现有的队列
#include
#include
#include
int msgget(key_t kint msgget(key_t key, int msgflg)
3.6 发送消息msgsnd()函数
一但获得了队列标识符,用户就可以在该消息队列上执行相关操作了,msgsnd()函数向队列传递消息
#include
#include
#include
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
3.7 接受消息msgrcv()函数
当获得队列标识符后,就可以开始在该消息队列上执行消息队列的接受操作,msgrcv()函数用于接收队列标识符中的消息
#include
#include
#include
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
3.8 消息控制msgctl()函数
为了在一个消息队列上执行控制操作,可以使用msgctl()函数
#include
#include
#include
int msgctl(int msqid, int cmd, struct msqid_ds *buf)
msgctl()向内核发送一个cmd命令,内核根据此来判断进行何种操作,buf为应用层和内核空间进行数据交换的指针
cmd值:
本例在建立消息队列后,打印其属性,并在每次发送和接收后均查看其属性,最后对消息队列进行修改5
#include
#include
#include
#include
#include
#include
#include
#include
void msg_show_attr(int msg_id, struct msqid_ds msg_info) /*打印消息属性的函数*/
{
int ret = -1;
sleep(1);
ret = msgctl(msg_id, IPC_STAT, &msg_info); /*获取消息*/
if( -1 == ret)
{
printf("获得消息信息失败\n"); /*获取消息失败,返回*/
return ;
}
printf("\n"); /*以下打印消息的信息*/
printf("现在队列中的字节数:%ld\n",msg_info.msg_cbytes); /*消息队列中的字节数*/
printf("队列中消息数:%d\n",(int)msg_info.msg_qnum); /*消息队列中的消息数*/
printf("队列中最大字节数:%d\n",(int)msg_info.msg_qbytes); /*消息队列中的最大字节数*/
printf("最后发送消息的进程pid:%d\n",msg_info.msg_lspid); /*最后发送消息的进程*/
printf("最后接收消息的进程pid:%d\n",msg_info.msg_lrpid); /*最后接收消息的进程*/
printf("最后发送消息的时间:%s",ctime(&(msg_info.msg_stime))); /*最后发送消息的时间*/
printf("最后接收消息的时间:%s",ctime(&(msg_info.msg_rtime))); /*最后接收消息的时间*/
printf("最后变化时间:%s",ctime(&(msg_info.msg_ctime))); /*消息的最后变化时间*/
printf("消息UID是:%d\n",msg_info.msg_perm.uid); /*消息的UID*/
printf("消息GID是:%d\n",msg_info.msg_perm.gid); /*消息的GID*/
}
int main(void)
{
int ret = -1;
int msg_flags, msg_id;
key_t key;
struct msgmbuf{ //消息的缓冲区结构
int mtype;
char mtext[10];
};
struct msqid_ds msg_info;
struct msgmbuf msg_mbuf;
memset(msg_mbuf.mtext, 0, 10);
int msg_sflags, msg_rflags;
char *msgpath = "/ipc/msg/"; //消息key产生所用的路径
key = ftok(msgpath, 'b');
if (key != -1)
{
printf("成功建立KEY\n");
}
else
{
printf("建立KEY失败/n");
}
msg_flags = IPC_CREAT|IPC_EXCL; //消息的类型
msg_id = msgget(key, msg_flags|0x0666); //建立消息
if (-1 == msg_id)
{
printf("建立消息失败\n");
return 0;
}
msg_show_attr(msg_id,msg_info); //显示消息属性
msg_sflags = IPC_NOWAIT;
msg_mbuf.mtype = 10;
memcpy(msg_mbuf.mtext, "123456", sizeof("123456"));
ret = msgsnd(msg_id, &msg_mbuf, sizeof("123456"), msg_sflags);
if (-1 == ret)
{
printf("发送消息失败\n");
}
msg_show_attr(msg_id, msg_info); //显示消息属性
msg_rflags = IPC_NOWAIT|MSG_NOERROR;
ret = msgrcv(msg_id, &msg_mbuf, 10, 10, msg_rflags); // 接收消息
if (-1 == ret)
{
printf("接收消息失败\n");
}
else
{
printf("接收消息成功,长度%d\n", ret);
}
msg_show_attr(msg_id, msg_info); //显示消息属性
msg_info.msg_perm.uid = 8;
msg_info.msg_perm.gid = 8;
msg_info.msg_qbytes = 12345;
ret = msgctl(msg_id, IPC_SET, &msg_info); //设置消息属性
if (-1 == ret)
{
printf("设置消息属性失败\n");
return 0;
}
msg_show_attr(msg_id, msg_info);
ret = msgctl(msg_id, IPC_RMID, NULL);
if (-1 == ret)
{
printf("删除消息失败\n");
return 0;
}
return 0;
}
接收消息失败
信号量是一种计数器,用来控制对多个进程共享的资源所进行的访问。它们常常被用做一个锁机制,在某个进程正在对特定资源进行操作时,信号量可以防止另一个进程去访问它。生产者和消费者的模型是信号量的典型使用
5.1 信号量数据结构
union semun{
int val; //信号量初始值
struct semid_ds *buf; //semid_ds结构指针
unsigned short int *array; //数组类型
struct seminfo *__buf; //信号量内部结构
};
5.2 新建信号量函数semget()
semget()函数用于创建一个新的信号量集合,或者访问现有的集合
#include
#include
#include
int semget(key_t key, int nsems, int semflg);
5.3 信号量操作函数semop()
信号量的P,V操作是通过向已经建立好的信号量,发送命令来完成的。向信号量发送命令的函数是semop()。
#include
#include
#include
int semop(int semid, struct sembuf *sops, unsigned nsops);
struct sembuf
{
unsigned short sem_num; //信号量的编号
short sem_op; //信号量的操作
short sem_flg; //信号量的操作标志
};
struct sembuf sem = {0, +1, NOWAIT}; //表示对信号量0,进行加1的操作
struct sembuf sem = {0, -1, NOWAIT}; //表示对信号量0,进行减1的操作
5.4 控制信号量参数semctl()函数
#include
#include
#include
int semctl(int semid, int semnum, int cmd, ...);
cmd取值:
5.5 信号量操作的例子
#include
#include
#include
#include
#include
typedef int sem_t;
union semun{
int val; //信号量初始值
struct semid_ds *buf; //semid_ds结构指针
unsigned short int *array; //数组类型
struct seminfo *__buf; //信号量内部结构
};
sem_t CreateSem(key_t key, int value) //创建信号量
{
union semun sem;
sem_t semid;
sem.val = value;
printf("%d\n", key);
semid = semget(key, 0, IPC_CREAT|0666);
if (-1 == semid)
{
printf("create semphore error\n");
return -1;
}
semctl(semid, 0, SETVAL, sem);
return semid;
}
int Sem_P(sem_t semid) //增加信号量
{
struct sembuf sops = {0, +1, IPC_NOWAIT};
return (semop(semid, &sops, 1));
}
int Sem_V(sem_t semid) //减小信号量
{
struct sembuf sops = {0, -1, IPC_NOWAIT};
return (semop(semid, &sops, 1));
}
void SetvalueSem(sem_t semid, int value) //设置信号量值
{
union semun sem;
sem.val = value;
semctl(semid, 0, SETVAL, sem);
}
int GetvalueSem(sem_t semid) //获取信号量值
{
union semun sem;
return semctl(semid, 0, GETVAL, sem);
}
void DestorySem(sem_t semid) //摧毁信号量
{
union semun sem;
sem.val = 0;
semctl(semid, 0, IPC_RMID, sem);
}
int main(void)
{
key_t key;
sem_t semid;
char i;
int value = 0;
key = ftok("/ipc/sem", 'a');
semid = CreateSem(key, 100);
for(i=0; i<3; i++)
{
Sem_P(semid);
Sem_V(semid);
}
value = GetvalueSem(semid);
printf("信号量的值为%d\n", value);
DestorySem(semid);
return 0;
}
信号量创建失败
共享内存是在多个进程之间共享内存区域的一种进程间的通信方式,它是在多个进程之间对内存段进行映射的方式实现内存共享的,是IPC最快捷的方式。
6.1 创建共享内存函数shmget()
shmget()用于创建一个新的共享的内存段,或者访问一个现有的共享内存段。
#include
#include
int shmget(key_t key, size_t size, int shmflg);
6.2 获得共享内存地址函数shmat()
#include
#include
void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);
如果shmaddr参数值等于0,则内核将试着查找一个未映射的区域,用户可以指定一个地址,但通常该地址只用于访问所拥有的硬件,或者解决与其他应用程序的冲突。
SHM_RND标志可以与标志参数进行OR操作,结果在置为标志参数,这样可以让传送的地址页对齐
6.3 删除共享内存函数shmdt()
#include
#include
void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);
当某进程不在需要一个共享内存段时,它必须调用这个函数来断开与该内存段的连接。在成功完成了断开连接操作以后,相关的shmid_ds结构的shm_nattch成员的值将减去1.如果这个值减到0,则内核将真正删除该内存段。
6.4 共享内存控制函数shmctl()
#include
#include
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
向共享内存的句柄发送命令来完成某种功能
命令值:
信号机制时UNIX系统中最为古老的进程之间的通信机制。它用于在一个或多个进程之间传递异步信号。信号可以由各种异步事件产生,例如键盘中断等。shell也可以使用信号将作业控制命令传递给它的子进程。
kill -l 列出所有的信号
7.1 信号截取函数signal()
signal()函数用于截取系统的信号,对此信号挂接用户自己的处理函数。
#include
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
signal()函数的原型说明此函数要求两个参数,返回一个函数指针,而该指针所指向的函数无返回值。第一个参数signo是一个整型数,第二个参数是函数指针,他所指向的函数需要一个整形参数,无返回值
7.2 向进程发送信号函数kill()和raise()
在挂接信号处理函数后,可以等待系统信号的到来。同时,用户可以自己构建信号发送到目标进程中。
#include
#include
int kill(pid_t pid, int sig);
int raise(int sig);
kill()函数向进程号为pid的进程发送信号,信号值为sig。当pid为0时,向当前系统的所有进程发送信号sig,即群发的意思。raise()函数在当前进程中自举一个信号sig,即向当前进程发送信号
1.1 多线程编程优点
1.2 编写Linux下的线程需要包含头文件pthread.h,在生成可执行文件的时候需要链接库libpthread.a或者libpthread.so
#include
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
3.1 函数pthread_join()用来等待一个线程运行结束。这个函数是阻塞函数,一直到被等待的线程结束为止,函数才返回并且收回被等待的资源
#include
int pthread_join(pthread_t thread, void **retval);
3.2 线程通过pthread_exit()结束,将结果传出
#include
void pthread_exit(void *retval);
retval:函数的返回值,可以被pthread_join捕获。
在用pthread_create()函数创建线程时,使用了默认参数,即将该函数的第二个参数设置为NULL。通常来说,建立一个线程的时候,使用默认属性就够了,但是很多shihou时候需要调整线程的属性,特别是线程的优先级。
4.1 线程的属性结构
typedef struct{
int detachstate; //线程的分离状态
int schedpolicy; //线程调度优先级
struct sched_param schedparam; //线程的调度参数
int inheritsched; //线程的继承性
int scope; //线程的作用域
size_t guardsize; //线程栈末尾的警戒缓冲区大小
int stackaddr_set; //运行栈
void * stackaddr; //线程栈的位置
size_t stacksize; //线程栈的大小
}pthread_attr_t;
线程的属性不能直接设置,需使用相关函数进行操作。线程属性的初始化函数为pthread_attr_init(),这个函数必须在pthread_create()之前调用。
属性对象主要包括线程的摘取状态,调度优先级,运行栈地址,运行栈大小,优先级
4.2 线程的优先级
线程的优先级由两个函数控制
int pthread_attr_getschedparam(const pthread_attr_t *attr, struct sched_param *param);
int pthread_attr_setschedparam(pthread_attr_t *attr, const struct sched_param *param);
线程的优先级存放在结构sched_param中。其操作方式是先将优先级取出来,然后对需要设置的参数修改后再写回去,这是对复杂结构进行设置的通用办法,防止因为设置不当造成不可预料的麻烦
4.3 线程的绑定状态
#include
int pthread_attr_setscope(pthread_attr_t *attr, int scope);
int pthread_attr_getscope(const pthread_attr_t *attr, int *scope);
4.4 线程的分离状态
线程的分离状态有分离线程和非分离线程,分离线程不用其他线程等待,当前线程运行结束后线程就结束了,并且马上释放资源。
#include
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
detachstate可以为分离线程或者非分离线程,PTHREAD_CREATE_DETACHED用于设置分离线程,PTHREAD_CREATE_JOINABLE用于设置非分离线程
互斥是用来保护一段临界区的,它可以保证某时间段内只有一个线程在执行一段代码或者访问某个资源
5.1 线程互斥的函数介绍
互斥的初始化函数:pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t * mutexattr)
互斥的锁定函数: pthread_mutex_lock(pthread_mutex_t *mutex)
互斥的预锁定函数:pthread_mutex_trylock(pthread_mutex_t *mutex)
互斥的解锁函数: pthread_mutex_unlock(pthread_mutex_t *mutex)
互斥的销毁函数: pthread_mutex_destroy(pthread_mutex_t *mutex)
函数pthread_mutex_init(),初始化一个mutex变量,函数pthread_mutex_lock()函数声明开始用互斥锁上锁,此后的代码直至调用pthread_mutex_unlock()函数为止,均不能执行被保护区域的代码。互斥锁使用完毕后要调用pthread_mutex_destory释放资源。
5.2 线程互斥函数的例子
该例子使用线程互斥的方法构建生产者和消费者,代码中建立了两个线程,函数producter_f()用于生产,函数consumer_f()用于消费。
#include
#include
#include
#include
int buffer_has_item = 0; //缓冲区计数值
pthread_mutex_t mutex;
int running = 1;
void *producter_f(void *arg)
{
while(running)
{
pthread_mutex_lock(&mutex);
buffer_has_item++;
printf("生产,总数量:%d\n",buffer_has_item);
pthread_mutex_unlock(&mutex);
}
}
void *consumer_f(void *arg)
{
while(running)
{
pthread_mutex_lock(&mutex);
buffer_has_item--;
printf("生产,总数量:%d\n",buffer_has_item);
pthread_mutex_unlock(&mutex);
}
}
int main()
{
pthread_t consumer_t; //消费者线程参数
pthread_t producter_t; //生产者线程参数
pthread_mutex_init(&mutex, NULL);
pthread_create(&producter_t, NULL, (void *)producter_f, NULL); //建立生产者线程
pthread_create(&consumer_t, NULL, (void *)consumer_f, NULL); //建立消费者线程
usleep(1);
running = 0;
pthread_join(consumer_t, NULL);
pthread_join(producter_t, NULL);
pthread_mutex_destroy(&mutex);
return 0;
}
信号量实际上是一个非负的整数计数器,用来实现对公共资源的控制。在公共资源增加的时候,信号量的值增加;公共资源消耗的时候,信号量的值减少;只有当信号量的值大于0的时候,才能访问信号量所代表的公共资源。
link -pthread
6.1 线程信号量初始化函数sem_init()
#include
int sem_init(sem_t *sem, int pshared, unsigned int value);
6.2 线程信号量增加函数sem_post()
该函数的作用是增加信号量的值,每次增加的值为1.当有线程等待这个信号量的时候,等待的线程将返回。
#include
int sem_post(sem_t *sem);
6.3 线程信号量等待函数sem_wait()
该函数的作用是减少信号量的值,如果信号量的值为0,则线程会一直阻塞到信号量的值大于0为止。每次使信号量的值减少1,当信号量的值为0时不在减少。
#include
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
6.4 线程信号量摧毁函数sem_destory()
该函数用来释放信号量sem
#include
int sem_destroy(sem_t *sem);
6.4 线程信号量的例子
一个线程增加信号量模仿生产者,一个线程减小信号量模仿消费者
#include
#include
#include
#include
sem_t sem;
int running = 1;
void *producter_f(void *arg)
{
int semval = 0;
while(running)
{
usleep(1);
sem_post(&sem);
sem_getvalue(&sem, &semval);
printf("生产,总数量:%d\n",semval);
}
}
void *consumer_f(void *arg)
{
int semval = 0;
while(running)
{
usleep(1);
sem_wait(&sem);
sem_getvalue(&sem, &semval);
printf("消费,总数量:%d\n",semval);
}
}
int main()
{
pthread_t consumer_t; //消费者线程参数
pthread_t producter_t; //生产者线程参数
sem_init(&sem, 0, 16);
pthread_create(&producter_t, NULL, (void *)producter_f, NULL); //建立生产者线程
pthread_create(&consumer_t, NULL, (void *)consumer_f, NULL); //建立消费者线程
sleep(1);
running = 0;
pthread_join(consumer_t, NULL);
pthread_join(producter_t, NULL);
sem_destroy(&sem);
return 0;
}
数值以交叉方式进行,有的时候产生多个在消耗多个。