linux进程和线程

进程

基本概念
进程是一个程序一次动态运行的过程,也是程序执行和资源管理分配的最小单位。
进程的生命周期
创建 -> 调度 -> 执行 -> 消亡
进程状态:就绪态、运行态、阻塞态等。
进程三要素
1、程序(指令和数据)
2、虚拟内存
3、PCB
PCB(进程控制块)在linux系统中用于记录进程的所有信息,实质上是task_struct类型的结构体,其数据结构主要包含的信息有
1、进程状态(State)
2、调度信息(Scheduling Information)
3、各种标识符(Identifiers)
4、进程间通信IPC(Inter_Process Communication)
5、时间和定时器(Times and Timers)
6、进程链接(Links)
7、文件系统(File System)
8、虚拟内存(Virtual Memory)
9、页面管理(page)
10、对称多处理器SMP
11、处理器相关环境(Processor Specific Context)
12、其它信息
进程分类
在这里插入图片描述

进程操作

创建进程

pid_t fork(void);
功能:当前进程创建一个子进程,本质是写时拷贝当前进程(父进程)在内存中0~3G的数据空间。
返回:在父进程中成功返回子进程ID(本质是int类型),失败返回-1并设errno值;在子进程中成功返回0。

写时拷贝cow(copy-on-write):只有在修改写入(改变数据)时才会真正拷贝。
获取进程ID

pid_t getpid(void);
功能:返回当前进程的ID
pid_t getppid(void);
功能:返回当前进程的父进程ID

示例

#include <stdio.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
	pid_t pid = fork();    //创建子进程
	if(pid < 0){    //创建失败
		perror("fork");
		return -1;    //结束进程
	}else if(pid > 0){    //此分支为父进程代码
		printf("This is parent process\n");
		printf("The PID of child is %d\n", pid);
	}else{    //此分支为子进程代码
		printf("This is child process\n");
		printf("The PID is %d\n", getpid());
		printf("The PID of parent is %d\n", getppid());	
    }
	return 0;    //结束进程
}

正常结束进程
1、main函数执行return
2、执行系统调用函数_exit或_Exit

#include <unistd.h>
void _exit(int status);
#include <stdlib.h>
void _Exit(int status);
功能:结束当前进程;关闭其所属的文件描述符;其子进程过继给init进程(PID==1);发送SIGCHLD信号给父进程(通知父进程回收该进程资源)。
参数status:通常取常量EXIT_SUCCESS或EXIT_FAILURE,作为该进程的结束状态返回给父进程
#define EXIT_FAILURE    1   /* Failing exit status.  */
#define EXIT_SUCCESS    0   /* Successful exit status.  */

3、执行标准C库函数exit

#include <stdlib.h>
void exit(int status);
功能:结束当前进程(本质是调用_exit);关闭其所属的文件流;刷新标准IO缓存。
参数status:取值同_exit,将(status & 0377)返回给父进程

进程等待

#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
功能:当前进程阻塞等待某个子进程的状态改变并获取相关信息;状态改变的场景包括子进程结束,子进程收到信号后停止或恢复运行;如果是子进程结束,父进程执行等待是允许系统释放其资源,否则子进程会保持僵尸态。
参数status:保存子进程的状态信息
返回:正常返回子进程ID,异常(包括没有子进程)返回-1。
pid_t waitpid(pid_t pid, int *status, int options);
功能:基本同wait
返回:正常返回子进程ID,如果设置非阻塞并且没有子进程改变状态则返回0,异常返回-1。
参数pid:若pid>0,则等待进程ID==pid的子进程;
若pid=0,则等待进程组ID==当前进程ID的子进程;
若pid=-1,则等待其任意一个子进程(同wait);
若pid<-1,则等待进程组ID==|pid|的子进程。
参数options:行为选项,通常取0到多个以下常量的按位或;
0(默认阻塞等待)
WNOHANG(非阻塞,立即返回)
WUNTRACED(如果有未被ptrace追踪的子进程结束,立即返回)
WCONTINUED(如果有子进程收到SIGCONT信号恢复运行,立即返回)
参数status:可以使用以下宏函数检查status;
WIFEXITED(status)
功能:如果子进程正常结束,返回true
WEXITSTATUS(status)
功能:仅当WIFEXITED返回true时使用,用于返回子进程退出状态,其数值为参数status的低8位
WIFSIGNALED(status)
功能:如果子进程被信号结束,返回true

孤儿进程:当某进程结束后,其子进程便成为孤儿进程,将自动托付给init进程管理。
僵尸进程(zombie):当某进程结束后,其父进程存在但未及时回收该进程资源,此时该进程便成为僵尸进程;如果僵尸进程遗留的资源占满内核调度表,将导致新进程无法被创建,因此要避免出现僵尸进程。
僵尸进程的解决方法
1、父进程调用wait/waitpid阻塞等待,但只能处理一个退出的子进程;
2、父进程忽略子进程的SIGCHLD信号,则由init进程回收其子进程遗留资源;

signal(SIGCHLD, SIG_IGN); // 当收到SIGCHLD时,忽略该信号

3、通过创建父子孙进程,然后利用孤儿进程自动托付给init进程管理;示例如下

pid_t pid1 pid2;
pid1 = fork(); // 创建子进程
if(pid1 < 0){  //子进程创建失败
    exit(EXIT_FAILURE);
}else if(pid1 > 0){  //父进程代码分支
	wait(NULL);  //阻塞等待子进程结束
}else{  //子进程代码分支
    pid2 = fork();  //子进程创建子进程(孙进程)
    if(pid2 < 0){  //孙进程创建失败
        exit(EXIT_FAILURE);
    }else if(pid2 > 0){  //子进程代码分支
        exit(EXIT_SUCCESS);  //子进程正常结束,孙进程自动被init进程接管
    }else{  //孙进程代码分支
        printf("The PID is %d\n", getpid());
    }
}

exec函数族

#include <unistd.h>
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 execvpe(const char *file, char *const argv[], char *const envp[]);
功能:在当前进程中执行另一段程序,函数一旦调用成功便不再返回;其本质是用新的程序代码覆盖当前进程的0~3G内存空间,只有进程ID保持不变。
参数path:带路径的可执行文件,例如"./temp/a.out"或者shell命令"/bin/ls"
参数file:不带路径的可执行文件,例如"a.out"或者shell命令"ls",系统会根据环境变量PATH寻找对应的程序
参数arg, ...:命令行参数列表,以NULL结束,例如"ls", "-l", NULL
参数argv:指向命令行参数列表的数组指针,例如
char *argv[] = {"ls", "-l", NULL};
参数envp:指向环境变量列表的数组指针(废弃原进程的环境变量),例如
char * envp[] = {"HOME=/home/linux", "PATH=/bin:/usr/bin", NULL};
返回:成功不返回,失败返回-1并设errno值。

当在linux命令行下运行程序时,其本质是shell解释器(一般是bash)调用fork创建子进程,然后子进程调用exec函数来执行程序

linux@ubuntu:~/work/temp$ ./a.out
hello world!

执行shell命令

#include <stdlib.h>
int system(const char *command);
功能:通过调用bash执行shell命令(本质是执行/bin/sh -c command),执行期间SIGCHLD信号将被阻塞,SIGINT和SIGQUIT信号将被忽略,执行结束后函数返回。
参数command:shell命令字符串,例如"ls -l"
返回:正常返回命令状态,异常返回-1;如果参数为NULL并且sh不可用则返回0

创建守护进程(daemon)
1、创建子进程后父进程结束,子进程托付给init进程管理。
2、创建会话组并成为组长,脱离终端。
3、改变当前进程的工作目录,避免其所在文件系统被卸载。
4、更改文件权限掩码,避免继承的权限掩码修改当前进程所创建文件的权限。
5、关闭所属的全部文件描述符,避免继承的文件描述符占用资源。
示例

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
int main(int argc, const char *argv[])
{
    pid_t pid;
	pid = fork();
	if(pid < 0){
        perror("fork");
        exit(1);
    }else if(pid > 0){
        exit(0); // 父进程结束
    }else{
        setsid();  // 创建会话组
        chdir("/home/linux/"); // 改变工作目录
        umask(0); // 文件权限掩码设为0
        int fd_num = getdtablesize(); //获取已打开的文件描述符数量(包括0)
        for(int i = 0; i < fd_num; i++){
            close(i); // 关闭所有的文件描述符
        }
        while(1){
            sleep(50);
        }
    }
    return 0;
}

进程间通信IPC

因为在linux系统中每个进程拥有独立的0-3G内存空间,所以进程间不能直接进行数据交互,但可以利用3-4G内核空间实现7种IPC机制。

无名管道

在内核中创建的64k大小管道空间,通过文件描述符进行读写,有独立的读端和写端(即半双工模式),如果向读端已关闭的管道写入数据会产生SIGPIPE信号,只能用于具有亲缘关系的进程之间通信。

#include <unistd.h>
int pipe(int pipefd[2]);
功能:创建无名管道
参数pipefd:管道创建成功后获取的读端pipefd[0]和写端pipefd[1],本质是文件描述符。
返回:成功返回0,失败返回-1并设errno值。

示例:子进程发送,父进程接收

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, const char *argv[])
{
	int fd[2];
	pid_t pid;
	if(-1 == pipe(fd)){  //创建无名管道
		perror("fail to pipe");
		exit(1);
	}

	pid = fork();  //创建子进程
	if(pid < 0){
		perror("fail to fork");
		exit(1);
	}else if(pid > 0){   //父进程
		wait(NULL);  //阻塞等待子进程结束
		close(fd[1]);  //关闭写端
		char buf[128] = {0};
		read(fd[0],buf,sizeof(buf));  //通过读端fd[0]执行读操作
		printf("parent recv:%s\n",buf);
		exit(0);
	}else{   // 子进程
		close(fd[0]);  //关闭读端
		char *s1 = "pipe test!";
		printf("child send:%s\n",s1);
		write(fd[1],s1,strlen(s1));  //通过写端fd[1]执行写操作
		exit(0);
	}
	return 0;
}

有名管道

实质是在文件系统中创建管道文件,通过文件IO进行读写,可以实现两个毫无关联的进程之间通信。

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
功能:创建有名管道(管道文件)
参数pathname:管道文件路径
参数mode:设置文件权限,实际最终文件的使用权限为(mode & ~umask)
返回:成功返回0,失败返回-1并设errno值;但是如果创建失败的原因是文件已存在,而该情况又不会影响进程通信,那么应当利用此时errno值为EEXIST做区分。

示例:进程1发送,进程2接收
进程1

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>
#include <fcntl.h>

int main(int argc, const char *argv[])
{
	if(0 != mkfifo("./myfifo",0666)){  //创建管道文件
		if(errno != EEXIST){  //排除文件已存在
			perror("fail to mkfifo");
			exit(1);
		}
	}
	int fd;
	char buf[128];
	fd = open("./myfifo",O_WRONLY);  //打开文件,只写
	if(fd == -1){
		perror("fail to open");
		exit(1);
	}
	while(1){
		bzero(buf,sizeof(buf));
		fgets(buf,sizeof(buf),stdin);
		write(fd,buf,strlen(buf));  //写操作
		if(strncmp(buf,"quit",4) == 0){
			exit(0);
		}
	}
	return 0;
}

进程2

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>
#include <fcntl.h>

int main(int argc, const char *argv[])
{
	if(0 != mkfifo("./myfifo",0666)){  //创建管道文件
		if(errno != EEXIST){  //排除文件已存在
			perror("fail to mkfifo");
			exit(1);
		}
	}
	int fd;
	char buf[128];
	fd = open("./myfifo",O_RDONLY);  //打开文件,只读
	if(fd == -1){
		perror("fail to open");
		exit(1);
	}
	while(1){
		bzero(buf,sizeof(buf));
		read(fd,buf,sizeof(buf));  //读操作
		printf("recv:%s\n",buf);
		if(strncmp(buf,"quit",4) == 0){
			exit(0);
		}
	}
	return 0;
}

信号

信号属于一种软中断,是由内核维护并传递给进程的,采用不可预测的异步机制。
内核支持如下种类的信号

linux@ubuntu:~$ kill -l
 1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP
 6) SIGABRT	 7) SIGBUS	 8) SIGFPE	 9) SIGKILL	10) SIGUSR1
11) SIGSEGV	12) SIGUSR2	13) SIGPIPE	14) SIGALRM	15) SIGTERM
16) SIGSTKFLT	17) SIGCHLD	18) SIGCONT	19) SIGSTOP	20) SIGTSTP
21) SIGTTIN	22) SIGTTOU	23) SIGURG	24) SIGXCPU	25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF	28) SIGWINCH	29) SIGIO	30) SIGPWR
31) SIGSYS	34) SIGRTMIN	35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3
38) SIGRTMIN+4	39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8
43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12	47) SIGRTMIN+13
48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14	51) SIGRTMAX-13	52) SIGRTMAX-12
53) SIGRTMAX-11	54) SIGRTMAX-10	55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7
58) SIGRTMAX-6	59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2
63) SIGRTMAX-1	64) SIGRTMAX

其中信号1~31 为不可靠信号,即可能没被进程接收或被进程忽略,且当同时收到多个相同的信号时进程只会处理其中一个;
而信号34~64为可靠信号,即一定会被进程接收并进行相应的处理。
信号的触发方式
1)硬件异常
2)终端操作
ctrl + c 给当前进程组发送信号2(SIGINT)
ctrl + \ 给当前进程组发送信号3(SIGQUIT)
ctrl + z 给当前进程组发送信号20(SIGTSTP)

给PID为4914的进程发送信号9(SIGKILL)
linux@ubuntu:~$ kill -9 4914

3)软件事件

进程对接收到的信号通常有以下4种处理方式
1)默认处理
2)忽略
3)捕获
4)阻塞
注:其中信号9(SIGKILL)、信号19(SIGSTOP)不可被忽略,不可被捕获,不可被阻塞,即不能使用2、3、4的处理方式。
信号有5类默认功能
Term:Default action is to terminate the process.
Ign:Default action is to ignore the signal.
Core:Default action is to terminate the process and dump core (see core(5)).
Stop:Default action is to stop the process.
Cont:Default action is to continue the process if it is currently stopped.

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
功能:向内核注册信号捕获
参数signum:指定的信号值
参数handler:一般为自定义的信号处理函数,当进程接收到信号时执行;如果是SIG_IGN,则进程忽略该信号;如果是SIG_DFL,则采取默认处理。
返回:成功则返回该信号的上一个处理函数指针(如果没有则为NULL),失败则返回SIG_ERR并设errno;如果需要使用此返回值(sighandler_t类型),则编译代码时要加上 -D_GNU_SOURCE。
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
功能:给进程发送信号
参数pid:指定的进程ID;
若pid>0,则发送给进程ID==pid的进程;
若pid=0,则发送给当前进程所属进程组的每个进程;
若pid=-1,则发送给当前进程有权限发送的每个进程(除了init进程);
若pid<-1,则发送给进程组ID==|pid|的每个进程。
参数sig:发送的信号值;如果为0则不发送信号,可用于检查进程或进程组ID是否存在。
返回:成功返回0,失败返回-1并设errno值
#include <signal.h>
int raise(int sig);
功能:给当前调用的进程或线程发送信号;在单线程的进程中等同于kill(getpid(), int sig),在多线程的进程中等同于pthread_kill(pthread_self(), int sig)。
参数sig:信号值
返回值:成功返回0,失败返回非0
#include <unistd.h>
unsigned int alarm(unsigned int seconds);
功能:定时给当前调用的进程发送一个SIGALRM信号,该信号默认终止进程。
参数seconds:定时多少秒;如果为0,则取消所有挂起的定时闹钟。
返回:通常返回上一次定时的剩余时间,如果没有则返回0
#include <unistd.h>
int pause(void);
功能:阻塞当前调用的进程或线程,一直到接收到任意信号。
返回:仅在信号被捕获且信号处理函数返回时才会返回,此时返回-1并设errno为EINTR。

示例:

#include <stdio.h>
#include <signal.h>

/* 信号处理函数 */
void sig_handler(int signo)
{
	if(signo == SIGQUIT){
		printf("ctrl+ \\ is used!\n");
	}else if(signo == SIGINT){
		printf("ctrl + c is used!\n");
	}else{
		printf("other signal is used!\n");
	}
}

int main(int argc, const char *argv[])
{
	signal(SIGQUIT,sig_handler);  //注册SIGQUIT信号捕获
	signal(SIGINT,sig_handler);  //注册SIGINT信号捕获
	signal(SIGKILL,sig_handler);  //注册SIGKILL信号捕获
	signal(SIGALRM,sig_handler);  //注册SIGALRM信号捕获
	alarm(5);  //定时5s发送SIGALRM信号
	while(1){
		sleep(1);
		printf("the process is alive\n");
	}
	return 0;
}

消息队列

消息队列的实现机制和管道类似,但又区别于管道,消息队列会将每次发送的数据打包并标上一个类型,这样接收端可以根据类型选择性接收;这里所说的消息队列以及后面的共享内存、信号量集属于System V标准。

#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
功能:生成4byte的System V IPC key值,其计算方式为(proj_id的低8位 | 文件所属的文件系统的次设备号的低8位 | pathname对应的inode号的低16位)。
参数pathname:文件路径,必须是已存在的可访问文件
参数proj_id:项目ID(非零值,只有低8位有效)
返回值:成功返回key值,失败返回-1并设errno值。

命令行查看消息队列(Message Queues)

linux@ubuntu:~/work$ ipcs -q

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages

删除消息队列

linux@ubuntu:~/work$ ipcrm -q msqid
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
功能:获取一个与key关联的消息队列;如果key设为IPC_PRIVATE或者msgflg指定了IPC_CREAT,则尝试创建新的消息队列。
参数key:键值(4byte的int型数值),一般通过ftok()获取,也可以设为IPC_PRIVATE。
参数msgflg:创建标志和权限属性,一般设为(IPC_CREAT | IPC_EXCL | mode),其中mode值可参考open()的参数mode。
返回:成功返回消息队列的标识符msqid,失败返回-1并设errno值;当消息队列已存在并且msgflg指定了(IPC_CREAT | IPC_EXCL)时则设errno为EEXIST。
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
功能:给队列发送消息,即数据入队
参数msqid:指定的消息队列标识符
参数msgp:消息结构体指针,结构体类型如下
struct msgbuf {
    long mtype;       /* message type, must be > 0 */
    char mtext[1];    /* message data */
};
参数msgsz:消息数据大小,即消息结构体成员mtext的大小;可以为0,即没有mtext。
参数msgflg:行为属性,通常设为0,当队列已满时会阻塞等待,直到空间可用;如果指定了IPC_NOWAIT,当队列已满时会立即返回失败并设errno为EAGAIN。
返回:成功返回0,失败返回-1并设errno值
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
功能:从队列接收消息,即数据出队
参数msqid:指定的消息队列标识符
参数msgp:消息结构体指针,用来保存接收的数据
参数msgsz:消息结构体成员mtext的大小,其决定数据接收的最大长度;如果消息长度大于msgsz,则丢弃超过的部分(参数msgflg指定了MSG_NOERROR)或者失败返回并设errno为E2BIG(msgflg未指定MSG_NOERROR)。
参数msgtyp:指定接收的消息类型,匹配消息结构体成员mtype;如果msgtyp > 0,则接收队列中类型为msgtyp的第一条消息(如果msgflg指定了MSG_EXCEPT,则接收队列中不等于msgtyp类型的第一条消息);如果msgtyp==0,则接收队列里的第一条消息;如果msgtyp < 0,则接收队列中不超过|msgtyp|的最小类型的第一条消息。
参数msgflg:行为属性,通常设为0,当队列中没有指定类型的消息时会阻塞等待,直到队列中有所需类型的消息;如果指定了IPC_NOWAIT,当队列中没有指定类型的消息时会立即返回失败并设errno为ENOMSG。
返回:成功返回实际获取的数据大小,失败返回-1并设errno值。
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
功能:操作消息队列
参数msqid:指定的消息队列标识符
参数cmd:指定的控制操作,常用值如下
IPC_STAT
获取与参数msqid相关的内核数据并保存在参数buf中;
IPC_SET
将参数buf中的数据写入与此消息队列相关的内核数据结构中;
IPC_RMID
删除此消息队列,唤醒所有发送或接收阻塞的进程(这些进程将返回错误并设errno为EIDRM)。
参数buf:结构体指针,指向数据缓存空间;如果cmd==IPC_RMID则可设为NULL。
返回:成功返回非负值(具体值取决于cmd),失败返回-1并设errno值。

示例:
进程1发送数据

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

typedef struct msgbuf
{
	long mtype;
	char buf[128];
}MSG;

int main(int argc, const char *argv[])
{

	key_t key;
	int msgid;
	MSG msg;
	int rv;

	key = ftok("/home/linux/",'a');//获取key值

	if(key == -1)
	{
		perror("ftok");
		exit(1);
	}

	msgid = msgget(key,IPC_CREAT | IPC_EXCL | 0664);//打开或者创建消息队列


	if(msgid == -1)
	{
		if(errno != EEXIST)//如果该消息队列存在重新打开
		{
			perror("msgget");
			exit(1);
		}
		else
		{
			msgid = msgget(key,0664);
		}
	}

	msg.mtype = 100;//设置发送消息的类型

	while(1)
	{

		bzero(msg.buf,sizeof(msg.buf));//清空数组

		fgets(msg.buf,sizeof(msg.buf),stdin);//从键盘上获取字符串写入msg.buf

		msg.buf[strlen(msg.buf) - 1] = '\0';

		rv = msgsnd(msgid,&msg,sizeof(MSG) - sizeof(long),0);//向指定的消息队列发送消息


		if(rv == -1)
		{
			perror("msgsnd");
			exit(1);
		}

		if(0 == strncmp(msg.buf,"quit;",4))
		{
			break;
		}
	}

	sleep(1);


	if(-1 == msgctl(msgid,IPC_RMID,NULL))//删除消息队列
	{
		perror("msgctl");
		exit(1);
	}

	/*
		//从消息队列中读取消息内容
		rv = msgrcv(msgid,&msg1,sizeof(MSG) - sizeof(long),100,0);

		if(rv == -1)
		{
			perror("msgrcv");
			exit(1);
		}

		printf("%s\n",msg1.buf);
	}*/
	return 0;
}

进程2接收数据

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

typedef struct msgbuf
{
	long mtype;
	char buf[128];
}MSG;

int main(int argc, const char *argv[])
{

	key_t key;
	int msgid;
	MSG msg;
	int rv;

	key = ftok("/home/linux/",'a');//获取key值

	if(key == -1)
	{
		perror("ftok");
		exit(1);
	}

	msgid = msgget(key,IPC_CREAT | IPC_EXCL | 0664);//打开或者创建消息队列


	if(msgid == -1)
	{
		if(errno != EEXIST)//如果该消息队列存在重新打开

		{
			perror("msgget");
			exit(1);
		}
		else
		{
			msgid = msgget(key,0664);
		}
	}


	while(1)
	{
		rv = msgrcv(msgid,&msg,sizeof(MSG) - sizeof(long),100,0);

		if(rv == -1)
		{
			perror("msgrcv");
			exit(1);
		}

		printf("%s\n",msg.buf);

		if(0 == strncmp(msg.buf,"quit",4))
		{
			break;
		}
	}
	return 0;
}

共享内存

共享内存的实现原理是申请一段真实的物理内存,然后分别映射到多个进程的虚拟内存缓冲区,这样多个进程操作的其实是同一段物理内存,因此当多进程访问共享内存时应该使用同步和互斥手段,可以利用信号或者共享内存中的变量实现进程间同步互斥。
命令行查看共享内存(Shared Memory Segments)

linux@ubuntu:~/work$ ipcs -m

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      
0x00000000 294912     linux      600        524288     2          dest         
0x00000000 1277953    linux      600        524288     2          dest         
0x00000000 425986     linux      600        524288     2          dest

删除共享内存

linux@ubuntu:~/work$ ipcrm -m shmid
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
功能:获取一块与key关联的共享内存,用法与msgget()类似
参数key:键值,用法参考msgget()
参数size:共享内存创建时的大小,至少4k(内存分页)
参数shmflg:创建标志和权限属性,用法参考msgget()的参数msgflg
返回:成功返回共享内存的标识符shmid,失败返回-1并设errno值;当共享内存已存在并且shmflg指定了(IPC_CREAT | IPC_EXCL)时则设errno为EEXIST。
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:将共享内存映射到当前进程的空间
参数shmid:共享内存标识符
参数shmaddr:指定映射的地址;如果设为NULL,则系统自动分配合适的映射地址;如果不为NULL同时shmflg又指定了SHM_RND标志位(取整对齐),则将地址偏移至低边界地址的整数倍上,即shmaddr-(shmaddr % SHMLBA),其中SHMLBA为低端边界常址。
参数shmflg:标志和权限属性;如果指定了SHM_RDONLY,则该进程对共享内存有只读权限。
返回值:成功则返回共享内存地址,失败则返回(void *)-1并设errno值。
int shmdt(const void *shmaddr);
功能:取消当前进程的某个共享内存映射
参数shmaddr:共享内存地址
返回值:成功返回0,失败返回-1并设errno值。
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能:操作共享内存
参数shmid:共享内存标识符
参数cmd:控制命令,常用值可参考msgctl()的参数cmd
参数buf:结构体指针,指向数据缓存空间;如果cmd==IPC_RMID则可设为NULL。
返回值:成功返回非负值(具体值取决于cmd),失败返回-1并设errno值。

示例:两个进程通过共享内存收发数据,并用信号做同步互斥

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/shm.h>
#include <errno.h>
#include <signal.h>
#include <strings.h>
#include <string.h>

typedef struct node
{
	pid_t pid_recv;
	pid_t pid_send;
	char buf[128];
}shm_t;

void sighand(int sig)
{
	;
}

int main(int argc, const char *argv[])
{
	key_t key;
	int shmid;
	shm_t *addr;
	key = ftok("/home/linux/",'a');
	if(key == -1)
	{
		perror("fail to ftok");
		exit(1);
	}

	shmid = shmget(key,sizeof(shm_t),IPC_CREAT|IPC_EXCL|0666);
	if(-1 == shmid)
	{
		if(errno == EEXIST)
		{
			shmid = shmget(key,sizeof(shm_t),0666);//创建不成功直接打开
		}
		else 
		{
			perror("fail to shmget");
			exit(1);
		}
	}

	system("ipcs -m");

	//映射
	addr = shmat(shmid,NULL,0);
	if(addr == (void *)-1)
	{
		perror("fail to shmat");
		exit(1);
	}

	system("ipcs -m");

	//注册信号
	signal(SIGUSR1,sighand);

	addr->pid_send = getpid();

	while(1)
	{
		bzero(addr->buf,sizeof(addr->buf));

		fgets(addr->buf,sizeof(addr->buf),stdin);
		
		kill(addr->pid_recv,SIGUSR2);
		if(strncmp(addr->buf,"quit",4) == 0)
			break;
		
		pause();
	}

	shmdt(addr);
	system("ipcs -m");
	return 0;
}
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdlib.h>
#include <sys/shm.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>

typedef struct node
{
	pid_t pid_recv;
	pid_t pid_send;
	char buf[128];
}shm_t;

void sighand(int sig)
{
	;
}

int main(int argc, const char *argv[])
{
	key_t key;
	int shmid;
	shm_t *addr;
	key = ftok("/home/linux/",'a');
	if(key == -1)
	{
		perror("fail to ftok");
		exit(1);
	}

	shmid = shmget(key,sizeof(shm_t),IPC_CREAT|IPC_EXCL|0666);
	if(-1 == shmid)
	{
		if(errno == EEXIST)
		{
			shmid = shmget(key,sizeof(shm_t),0666);//创建不成功直接打开
		}
		else 
		{
			perror("fail to shmget");
			exit(1);
		}
	}

	system("ipcs -m");

	//映射
	addr = shmat(shmid,NULL,0);
	if(addr == (void *)-1)
	{
		perror("fail to shmat");
		exit(1);
	}

	system("ipcs -m");

	//注册
	signal(SIGUSR2,sighand);

	//dosomething
	addr->pid_recv = getpid();

	//先阻塞,后接收,发信号SIGUSR1
	while(1)
	{
		pause();
		printf("recv:%s\n",addr->buf);

		if(strncmp(addr->buf,"quit",4) == 0)
			break;
		kill(addr->pid_send,SIGUSR1);
	}

	shmdt(addr);
	shmctl(shmid,IPC_RMID,NULL);
	system("ipcs -m");
	return 0;
}

信号量集

本质上是信号量数组,可实现进程间同步互斥。
命令行查看信号量集(Semaphore Arrays)

linux@ubuntu:~/work$ ipcs -s

------ Semaphore Arrays --------
key        semid      owner      perms      nsems

删除信号量集

linux@ubuntu:~/work$ ipcrm -s semid
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
功能:获取一个与key关联的信号量集,用法与msgget()相似。
参数key:键值
参数nsems:信号量集创建时包含多少个信号量
参数semflg:创建标志和权限属性,用法参考msgget()的参数msgflg。
返回值:成功返回信号量集标识符semid,失败返回-1并设置errno值;当信号量集已存在并且semflg指定了(IPC_CREAT | IPC_EXCL)时则设errno为EEXIST。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ...);
功能:对某个信号量集执行cmd指定的控制操作 
参数semid:信号量集标识符
参数semnum:指定信号量集的第几个信号量
参数cmd:控制命令,常用值可参考msgctl()的参数cmd,还有
SETVAL
将集合里第semnum个信号量的值semval设为union semun联合体成员val的值。
GETVAL
返回第semnum个信号量的值semval。
SETALL
将集合里所有信号量的值semval设为union semun联合体成员array的值。
GETALL
获取集合里所有信号量的值semval并保存在联合体成员array数组中。
参数...:该参数是否存在取决于cmd的值;如果cmd为IPC_RMID,则省略此参数;如果需要此参数,则其类型为union semun联合体(调用进程自定义)。
union semun {
    int              val;    /* Value for SETVAL */
    struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
    unsigned short  *array;  /* Array for GETALL, SETALL */
    struct seminfo  *__buf;  /* Buffer for IPC_INFO(Linux-specific) */
};
返回值:成功返回非负值(具体值取决于cmd),失败返回-1并设置errno值。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, unsigned nsops);
功能:对某个信号量集里指定的信号量执行操作
参数semid:信号量集标识符
参数sops:struct sembuf 类型的结构体指针,指向该结构体数组的首地址(需要初始化结构体成员);其结构体成员包括
    unsigned short sem_num;  /* semaphore number */
    short          sem_op;   /* semaphore operation */
    short          sem_flg;  /* operation flags */
sem_op取值如下
> 0 时,进行V操作,即semval += sem_op,表示释放信号量;
== 0 时,当前进程阻塞等待直到semval为0;
< 0 时,进行P操作,即semval -= |sem_op|,表示持有信号量,如果(semval - |sem_op|) < 0则会阻塞等待直到信号量可用;
sem_flg常用值如下
== 0 时,设置信号量为默认操作;
== IPC_NOWAIT 时,设置信号量为非阻塞,如果进程获取不到信号量则直接返回EAGAIN错误;
== SEM_UNDO 时,该选项会让内核记录一个与调用进程相关的UNDO记录,如果该进程崩溃,则根据这个进程的UNDO记录自动恢复相应信号量的计数值。
参数nsops:操作的信号量个数,也即sops指向的结构体数组成员个数。
返回值:成功返回0,失败返回-1并设置errno值。

示例:进程间使用信号量集实现同步访问共享内存
自定义信号量操作函数

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdlib.h>
#include <errno.h>

int mysem_init(int semid,int n,int value)
{
	union semun {
		int		val;	/* Value for SETVAL */
		struct semid_ds *buf;	/* Buffer for IPC_STAT, IPC_SET */
		unsigned short  *array;	/* Array for GETALL, SETALL */
		struct seminfo  *__buf;	/* Buffer for IPC_INFO(Linux-specific) */
	};

	union semun buf;
	buf.val = value; //信号量的初始值,通过main传参

	if(-1 == semctl(semid,n,SETVAL,buf))
	{
		perror("fail to semctl");
		return -1;
	}
	return 0;
}

int mysem_wait(int semid,int n)
{
	struct sembuf buf;
	buf.sem_num = n; //指定第几个信号量,通过main传参
	buf.sem_op = -1; //信号量p操作,即信号量的值-1
	buf.sem_flg = SEM_UNDO;

	if(-1 == semop(semid,&buf,1))
	{
		perror("fail to semop");
		return -1;
	}
	return 0;
}

int mysem_post(int semid,int n)
{
	struct sembuf buf;
	buf.sem_num = n; //指定第几个信号量,通过main传参
	buf.sem_op = 1; //信号量v操作,即信号量的值+1
	buf.sem_flg = SEM_UNDO;

	if(-1 == semop(semid,&buf,1))
	{
		perror("fail to semop");
		return -1;
	}
	return 0;
}

进程1发送数据至共享内存

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdlib.h>
#include <sys/shm.h>
#include <errno.h>
#include <strings.h>
#include <string.h>

typedef struct node
{
	char buf[128];
}shm_t;   //定义共享内存的结构体

int main(int argc, const char *argv[])
{
	key_t key;
	int shmid,semid;
	shm_t *addr;
	key = ftok("/home/linux/",'a');
	if(key == -1)
	{
		perror("fail to ftok");
		exit(1);
	}

	//创建共享内存
	shmid = shmget(key,sizeof(shm_t),IPC_CREAT|IPC_EXCL|0666);
	if(-1 == shmid)
	{
		if(errno == EEXIST)
		{
			shmid = shmget(key,sizeof(shm_t),0666);//创建不成功直接打开
		}
		else 
		{
			perror("fail to shmget");
			exit(1);
		}
	}

	system("ipcs -m");

	//创建信号量集
	semid = semget(key,2,IPC_CREAT|IPC_EXCL|0666);
	if(semid == -1)
	{
		if(errno == EEXIST)
		{
			semid = semget(key,2,0666);
		}
		else 
		{
			perror("fail to semget");
			exit(1);
		}
	}
	system("ipcs -s");
	//映射
	addr = shmat(shmid,NULL,0);
	if(addr == (void *)-1)
	{
		perror("fail to shmat");
		exit(1);
	}
	while(1)
	{
		mysem_wait(semid,0);//sem0实行p操作
		bzero(addr->buf,sizeof(addr->buf));
		fgets(addr->buf,sizeof(addr->buf),stdin);

		mysem_post(semid,1);//sem1实行v操作
		if(strncmp(addr->buf,"quit",4) == 0)
			break;
	}
	shmdt(addr);
	system("ipcs -m");
	system("ipcs -s");
	return 0;
}

进程2从共享内存中获取数据

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdlib.h>
#include <sys/shm.h>
#include <errno.h>
#include <string.h>

typedef struct node
{
	char buf[128];
}shm_t;   //定义共享内存的结构体

int main(int argc, const char *argv[])
{
	key_t key;
	int shmid,semid;
	shm_t *addr;
	key = ftok("/home/linux/",'a');
	if(key == -1)
	{
		perror("fail to ftok");
		exit(1);
	}

	//创建共享内存
	shmid = shmget(key,sizeof(shm_t),IPC_CREAT|IPC_EXCL|0666);
	if(-1 == shmid)
	{
		if(errno == EEXIST)
		{
			shmid = shmget(key,sizeof(shm_t),0666);//创建不成功直接打开
		}
		else 
		{
			perror("fail to shmget");
			exit(1);
		}
	}
	system("ipcs -m");
	//创建信号量集
	semid = semget(key,2,IPC_CREAT|IPC_EXCL|0666);
	if(semid == -1)
	{
		if(errno == EEXIST)
		{
			semid = semget(key,2,0666);
		}
		else 
		{
			perror("fail to semget");
			exit(1);
		}
	}
	system("ipcs -s");
	//映射
	addr = shmat(shmid,NULL,0);
	if(addr == (void *)-1)
	{
		perror("fail to shmat");
		exit(1);
	}
	//do someting
	//初始化  
	mysem_init(semid,0,1);//sem0的值设为1
	mysem_init(semid,1,0);//sem1的值设为0
	while(1)
	{
		mysem_wait(semid,1);//sem1实行p操作
		printf("recv:%s\n",addr->buf);
		if(strncmp(addr->buf,"quit",4) == 0)
			break;

		mysem_post(semid,0);//sem0实行v操作
	}
	shmdt(addr);
	shmctl(shmid,IPC_RMID,NULL);//删除共享内存
	semctl(semid,0,IPC_RMID); //删除信号量集

	system("ipcs -m");
	system("ipcs -s");
	return 0;
}

套接字

套接字属于BSD标准(Berkeley Software Distribution),其本质上是一种文件描述符,通常用于网络通信(网络套接字),也可用于本地进程间通信(Unix域套接字)。

#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
功能:创建套接字
参数domain:指定通信域,常用值如下
AF_UNIX / AF_LOCAL    本地进程间通信
AF_INET               IPv4网络协议
AF_INET6              IPv6网络协议
AF_NETLINK        用户进程与内核进程通信
参数type:指定套接字的类型,常用值如下
SOCK_STREAM(流式套接字)
提供有序、可靠、双向、基于连接的字节流(例如TCP)。
SOCK_DGRAM(数据报套接字) 
支持数据报,即最大长度固定、无连接、不可靠的消息(例如UDP)。 
参数protocol:指定与套接字配合使用的特定协议;一般使用系统已封装的协议,所以通常设为0。
返回:成功返回一个文件描述符(套接字),失败返回-1并设errno值。
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:当前主机地址绑定套接字
参数sockfd:指定的套接字
参数addr:包含地址等信息的结构体指针,struct sockaddr结构体类型如下
struct sockaddr {
    sa_family_t sa_family; //通信域,取值与socket()的参数domain一致
    char        sa_data[14]; //包含地址和端口等信息
};
因为struct sockaddr类型包含的信息较为笼统,所以实际赋值传参一般使用struct sockaddr_in类型(网络通信)和struct sockaddr_un类型(本地通信);
struct sockaddr_in {
    sa_family_t    sin_family; /* address family: AF_INET */
    in_port_t      sin_port;   /* port in network byte order */
    struct in_addr sin_addr;   /* internet address */
};
struct in_addr {
    uint32_t       s_addr;     /* address in network byte order */
};
其中sin_port为端口号,sin_addr.s_addr为IP地址,两者都必须使用网络字节序NBO(network byte order),而在可视化代码中通常使用主机字节序HBO(host byte order),所以在赋值和取值时要注意字节序转化,示例如下
端口号的字节序转化:
sin_port = htons(8080); // HBO -> NBO
hbo_val = ntohs(sin_port); // NBO -> HBO
IP地址的字节序转化:
sin_addr.s_addr = inet_addr("192.168.1.2"); // IP地址格式(点分十进制)的字符串 -> NBO
(char *)s_ip = inet_ntoa(sin_addr); // NBO -> IP地址格式的字符串
struct sockaddr_un {
    sa_family_t sun_family; /*AF_UNIX*/
    char sun_path[108]; /*pathname*/
};
其中sun_path为本地socket类型的文件路径,一般在 /tmp 路径下,该文件在绑定Unix域套接字时被创建。
参数addrlen:地址长度,即addr指向的数据大小。
返回:成功返回0,失败返回-1并设errno值。

数据报套接字通信模型
数据报套接字

#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
功能:发送消息
参数sockfd:发送端的套接字(文件描述符)
参数buf:消息数据的首地址
参数len:消息数据的长度(byte)
参数flags:指定附加功能,通常设0到多个以下标志的按位或
MSG_DONTWAIT
启用非阻塞操作,如果发送阻塞则返回 EAGAIN 或 EWOULDBLOCK;
MSG_DONTROUTE
不使用网关发送数据包,只发送到直连网络上的主机;
参数dest_addr:包含接收端地址等信息的结构体指针
参数addrlen:dest_addr指向的数据的长度
返回:成功返回发送的字节数,失败返回-1并设errno值。
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
功能:接收消息
参数sockfd:接收端的套接字
参数buf:保存接收的消息数据
参数len:buf指向的空间大小
参数flags:指定附加功能,通常设0到多个以下标志的按位或
MSG_DONTWAIT
启用非阻塞操作,如果接收阻塞则返回 EAGAIN 或 EWOULDBLOCK;
MSG_TRUNC
返回数据包/数据报的实际长度;
MSG_WAITALL
阻塞等待直到接收完所请求的数据;
参数src_addr:保存发送端的地址等信息
参数addrlen:保存发送端的地址等信息的实际长度
返回:成功返回接收到的数据长度,失败返回-1并设errno值;如果发送端执行了有序关闭则返回0。

示例:Unix域套接字通信之数据报套接字
客户端进程

#include<stdio.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<stdlib.h>
#include<string.h>

#define UDS_PATH "/tmp/server_dgram_socket"

int main(int argc, const char *argv[])
{
	int sockfd;
    //创建套接字
	sockfd = socket(AF_UNIX, SOCK_DGRAM, 0);
	if(sockfd == -1)
	{
		perror("socket");
		exit(1);
	}
    //填充服务器地址信息
	struct sockaddr_un seraddr;
	memset(&seraddr,0,sizeof(seraddr));
	seraddr.sun_family = AF_UNIX;
	strcpy(seraddr.sun_path, UDS_PATH);

	char buf[10];
	socklen_t len = sizeof(seraddr);
	while(1)
	{
		fgets(buf,sizeof(buf),stdin);
		buf[strlen(buf) - 1] = '\0';//去掉\n
		//向服务器发送数据
		sendto(sockfd,buf,strlen(buf) + 1,0,(const struct sockaddr *)&seraddr,len);

		if(strncmp(buf,"quit",4) == 0)
			break;
	}

	close(sockfd);
	return 0;
}

服务器进程

#include<stdio.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<stdlib.h>
#include<string.h>

#define UDS_PATH "/tmp/server_dgram_socket"

int main(int argc, const char *argv[])
{
	int sockfd;
    //创建套接字
	sockfd = socket(AF_UNIX, SOCK_DGRAM, 0);

    //填充服务器地址信息
   	struct sockaddr_un seraddr, cliaddr;
	memset(&seraddr,0,sizeof(seraddr));
	seraddr.sun_family = AF_UNIX;
	strcpy(seraddr.sun_path, UDS_PATH);

    //绑定套接字
	bind(sockfd, (const struct sockaddr *)&seraddr, sizeof(seraddr));

	ssize_t n;
	char buf[10];
	socklen_t len;
	while(1)
	{
		memset(buf,0,sizeof(buf));
		//从客户端接收数据
		n = recvfrom(sockfd,buf,sizeof(buf),0,(struct sockaddr *)&cliaddr,&len);
		if(strncmp(buf,"quit",4) == 0)
		{
			printf("客户端退出\n");
			continue;
		}
		printf("n = %d\n",n);
		printf("recv data:%s\n", buf);

	}
	return 0;
}

线程

基本概念
线程是进程的基本执行单元,相当于是带有时间片的函数(能调度的执行体),一个进程可以并发执行多个线程。
线程和进程的区别
进程:资源分配的最小单位,各进程间的资源独立。
线程:资源调度的最小单位,多个线程共享一个进程的资源,包括进程ID、堆区和全局变量等,但每个线程在进程的缓冲区都有独立的线程ID、线程栈、局部变量、程序计数器和errno等。

在linux中,线程相关接口是由单独的动态库(libpthread.so)来提供的,因此使用前可能要安装线程库

linux@ubuntu:~/work/temp$ sudo apt-get install manpages-posix
linux@ubuntu:~/work/temp$ sudo apt-get install manpages-posix-dev

编译源码时则需要链接线程库

linux@ubuntu:~/work/temp$ gcc main.c -lpthread
或者
linux@ubuntu:~/work/temp$ gcc main.c -pthread  // 兼容其它类Unix系统

线程操作

线程创建

#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
功能:执行一个新线程
参数thread:分配的线程ID,本质上是个无符号长整型的全局变量
参数attr:设置的线程属性,设为NULL表示默认属性
参数start_routine:线程函数的入口地址,本质上是个函数指针
参数arg:执行时传递给新线程的参数,设为NULL则不传参
返回:成功返回0,失败返回线程errno

线程退出

#include <pthread.h>
void pthread_exit(void *retval);
功能:退出当前线程
参数retval:返回的退出状态,没有则设为NULL

线程阻塞

#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
功能:等待指定的线程退出
参数thread:指定的线程ID
参数retval:接收线程的退出状态,本质上是指针的地址,不需要可设为NULL
返回:成功返回0,失败返回线程errno

线程取消

#include <pthread.h>
int pthread_cancel(pthread_t thread);
功能:向指定的线程发送取消请求
参数thread:指定的线程ID
返回:成功返回0,失败返回线程errno

示例

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>

pthread_t tid1,tid2,tid3;

void* fun1(void *arg)  // 定义线程函数1
{
	printf("tid1 args:%s\n",(char *)arg);
	while(1){
        printf("I'm pthread1!\n");
    	sleep(1);
	}
	pthread_exit(NULL); // 退出线程
}

void* fun2(void *arg)  // 定义线程函数2
{
    printf("tid2 args:%s\n",(char *)arg);	
	while(1){  
	    printf("I'm pthread2!\n");
		sleep(1);
	}
	pthread_exit(NULL); // 退出线程
}

void* fun3(void *arg)  // 定义线程函数3
{
	sleep(5);
	pthread_cancel(tid1); // 请求取消线程1
	pthread_cancel(tid2); // 请求取消线程2
	pthread_exit(NULL); // 退出线程
}

int main(int argc, const char *argv[])
{
	char *s1 = "create pthread1!";	
	char *s2 = "create pthread2!";
	int ret=pthread_create(&tid1,NULL,fun1,s1); // 创建线程1
	if(ret != 0){
		fprintf(stderr,"fail to create tid1:%d\n",ret);
		exit(1);
	}

	ret = pthread_create(&tid2,NULL,fun2,s2); // 创建线程2
	if(ret != 0){
		fprintf(stderr,"fail to create tid2:%d\n",ret);
		exit(1);
	}

	ret = pthread_create(&tid3,NULL,fun3,NULL); // 创建线程3
	if(ret != 0){
		fprintf(stderr,"fail to create tid3:%d\n",ret);
		exit(1);
	}
	char *rt1 = NULL;
	char *rt2 = NULL;
	char *rt3 = NULL;	
	pthread_join(tid1,(void **)&rt1); // 等待线程1结束
	pthread_join(tid2,(void **)&rt2); // 等待线程2结束
	pthread_join(tid3,(void **)&rt3); // 等待线程3结束
	return 0;
}

同步和互斥

当多线程或多进程同时使用临界资源(共享资源)时容易出现异常,因此需要用同步和互斥的手段避免资源竞争。
同步:多个任务按照某一个指定的顺序执行
互斥:多个任务在同一时间点只能执行其中一个

信号量

信号量抽象为共享资源的可用数量,仅能通过两个原子操作(PV操作)来访问,这里所讲的信号量属于POSIX标准(Portable Operating System Interface of UNIX)。

#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
功能:初始化信号量
参数sem:信号量取址
参数pshared:用于指示此信号量是否进程间共享,如果为非零值表示进程间共享,否则表示线程间共享,通常设为0。
参数value:信号量的初始值,大小不超过SEM_VALUE_MAX
/* Maximum value the semaphore can have.  */
#define SEM_VALUE_MAX   (2147483647)
返回:正常返回0,异常返回-1并设errno值
#include <semaphore.h>
int sem_wait(sem_t *sem);
功能:P操作(源自荷兰语passeren),如果信号量值为0,则阻塞等待直到信号量值>0;若信号量值>0,则将信号量值自减1(持有信号量)且继续往下执行。
参数:信号量取址
返回:正常返回0,异常返回-1并设errno值
#include <semaphore.h>
int sem_post(sem_t *sem);
功能:V操作(源自荷兰语vrijgeven),将信号量值自加1(释放信号量)
参数:信号量取址
返回:正常返回0,异常返回-1并设errno值
#include <semaphore.h>
int sem_destroy(sem_t *sem);
功能:销毁信号量
参数:信号量取址
返回:正常返回0,异常返回-1并设errno值

示例:实现同步

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdlib.h>

pthread_t tid1,tid2;
sem_t sem1,sem2;

void* fun1(void *arg)
{
    while(1){
        sem_wait(&sem1); //等待信号量1可用
        printf("pthread1 is running!\n"); //临界区
        sleep(1);
        sem_post(&sem2); //释放信号量2
    }
    pthread_exit(NULL);
}

void* fun2(void *arg)
{
    while(1){
        sem_wait(&sem2); //等待信号量2可用
        printf("pthread2 is running!\n"); //临界区
        sleep(1);
        sem_post(&sem1); //释放信号量1
    }
    pthread_exit(NULL);
}

int main(int argc, const char *argv[])
{
    char *s1 = "create pthread1!";	
    char *s2 = "create pthread2!";
    char *rt1 = NULL;
    char *rt2 = NULL;

    int ret_sem = sem_init(&sem1,0,1); //信号量1初始化为1
    if(ret_sem == -1){
        perror("fail to sem_init");
        exit(1);
    }

    ret_sem = sem_init(&sem2,0,0); //信号量2初始化为0
    if(ret_sem == -1){
        perror("fail to sem_init");
        exit(1);
    }

    int ret=pthread_create(&tid1,NULL,fun1,s1); //创建线程1
    if(ret != 0){
        fprintf(stderr,"fail to create tid1:%d\n",ret);
        exit(1);
    }

    ret = pthread_create(&tid2,NULL,fun2,s2); //创建线程2
    if(ret != 0){
        fprintf(stderr,"fail to create tid2:%d\n",ret);
        exit(1);
    }

    pthread_join(tid1,(void **)&rt1); //等待线程1结束
    pthread_join(tid2,(void **)&rt2); //等待线程2结束
    sem_destroy(&sem1); //销毁信号量1
    sem_destroy(&sem2); //销毁信号量2
    return 0;
}

示例:实现互斥

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdlib.h>

pthread_t tid1,tid2;
sem_t sem1;

void* fun1(void *arg)
{
    while(1){
        sem_wait(&sem1); //等待信号量1可用
        printf("pthread1 is running!\n"); //临界区
        sleep(1);
        sem_post(&sem1); //释放信号量1
    }
    pthread_exit(NULL);
}

void* fun2(void *arg)
{
    while(1){
        sem_wait(&sem1); //等待信号量1可用
        printf("pthread2 is running!\n"); //临界区
        sleep(1);
        sem_post(&sem1); //释放信号量1
    }
    pthread_exit(NULL);
}

int main(int argc, const char *argv[])
{
    char *s1 = "create pthread1!";	
    char *s2 = "create pthread2!";
    char *rt1 = NULL;
    char *rt2 = NULL;

    int ret_sem = sem_init(&sem1,0,1); //信号量1初始化为1
    if(ret_sem == -1){
        perror("fail to sem_init");
        exit(1);
    }

    int ret=pthread_create(&tid1,NULL,fun1,s1); //创建线程1
    if(ret != 0){
        fprintf(stderr,"fail to create tid1:%d\n",ret);
        exit(1);
    }

    ret = pthread_create(&tid2,NULL,fun2,s2); //创建线程2
    if(ret != 0){
        fprintf(stderr,"fail to create tid2:%d\n",ret);
        exit(1);
    }

    pthread_join(tid1,(void **)&rt1); //等待线程1结束
    pthread_join(tid2,(void **)&rt2); //等待线程2结束
    sem_destroy(&sem1); //销毁信号量1
    return 0;
}

互斥锁

#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
功能:初始化锁
参数mutex:互斥锁取址
参数attr:互斥锁的属性,NULL为默认属性
返回:正常返回0,异常返回对应的errno值。
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
功能:上锁,即阻塞等待直到锁可用
参数mutex:互斥锁取址
返回:正常返回0,异常返回对应的errno值。
此外还有如下的非阻塞模式,即如果锁当前不可用,则立即返回EBUSY。
int pthread_mutex_trylock(pthread_mutex_t *mutex);
#include <pthread.h>
int pthread_mutex_unlock(pthread_mutex_t *mutex);
功能:解锁,即释放持有的锁
参数mutex:互斥锁取址
返回:正常返回0,异常返回对应的errno值。
#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);
功能:销毁互斥锁
参数mutex:互斥锁取址
返回:正常返回0,异常返回对应的errno值。

示例:实现互斥

#include <stdio.h>
#include <pthread.h>

pthread_t tid1,tid2;
pthread_mutex_t  mutex1;
int i = 0,a = 0,b = 0;

void* pth1(void *arg)
{
	while(1){
		pthread_mutex_lock(&mutex1); // 上锁
		i++;
		a = i;
		b = i;
		printf("pthread1 is running!\n");
		pthread_mutex_unlock(&mutex1); // 解锁
	}
	pthread_exit(NULL);
}

void* pth2(void *arg)
{
	while(1){
		pthread_mutex_lock(&mutex1); // 上锁
		if(a!=b){
			printf("i=%d a=%d b=%d\n",i,a,b);
		}
		printf("pthread2 is running!\n");
		pthread_mutex_unlock(&mutex1); // 解锁
	}
	pthread_exit(NULL);
}

int main(int argc, const char *argv[])
{
	pthread_mutex_init(&mutex1,NULL); // 初始化锁
	pthread_create(&tid1,NULL,pth1,NULL);
	pthread_create(&tid2,NULL,pth2,NULL);

	pthread_join(tid1,NULL);
	pthread_join(tid2,NULL);
	pthread_mutex_destroy(&mutex1); // 销毁互斥锁
	return 0;
}

条件变量

条件变量只能与互斥锁配合使用,可实现同步。

#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
功能:初始化条件变量
参数cond:条件变量取址
参数attr:条件变量的属性,NULL为默认属性
返回值:正常返回0,异常返回对应的errno值。
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
功能:阻塞当前线程后解锁,等待条件变量到达后再上锁
参数cond:条件变量取址
参数mutex:互斥锁取址
返回值:正常返回0,异常返回对应的errno值。
int pthread_cond_signal(pthread_cond_t *cond);
功能:唤醒一个正在等待条件变量的线程
参数cond:条件变量取址
返回值:正常返回0,异常返回对应的errno值。
int pthread_cond_broadcast(pthread_cond_t *cond);
功能:唤醒所有正在等待条件变量的线程
参数cond:条件变量取址
返回值:正常返回0,异常返回对应的errno值。
int pthread_cond_destroy(pthread_cond_t *cond);
功能:销毁条件变量
参数cond:条件变量取址
返回值:正常返回0,异常返回对应的errno值。

示例:实现同步

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <strings.h>

pthread_t tid1,tid2,tid3;
pthread_mutex_t  mutex1;
pthread_cond_t cond1;

int i = 0,a = 0,b = 0;
char *buf;

void* pth3(void *arg)
{
	while(1)
	{
	//	bzero(buf,128);
		printf("(pth3) please input:\n");
		fgets(buf,128,stdin);
	//	pthread_cond_signal(&cond1);
		pthread_cond_broadcast(&cond1);
	}
	pthread_exit(NULL);
}

void* pth1(void *arg)
{
	while(1)
	{
		pthread_mutex_lock(&mutex1); //上锁
		pthread_cond_wait(&cond1,&mutex1); //等待条件变量
		printf("pth1 buf:%s\n",buf);
		if(strncmp(buf,"quit",4)==0)//比较字符串前n个字符
		{
			exit(0);
		}
		pthread_mutex_unlock(&mutex1); //解锁
	}
	pthread_exit(NULL);
}

void* pth2(void *arg)
{
	while(1)
	{
		pthread_mutex_lock(&mutex1); //上锁
		pthread_cond_wait(&cond1,&mutex1); //等待条件变量
		printf("pth2 buf:%s\n",buf);
		if(strncmp(buf,"quit",4)==0)//比较字符串前n个字符
		{
			exit(0);
		}
		
		pthread_mutex_unlock(&mutex1); //解锁
	}
	pthread_exit(NULL);
}

int main(int argc, const char *argv[])
{
	pthread_mutex_init(&mutex1,NULL);//创建互斥锁
	pthread_cond_init(&cond1,NULL);//创建条件变量	
	buf = malloc(sizeof(char)*128); //申请堆区空间

	pthread_create(&tid3,NULL,pth3,NULL);
	pthread_create(&tid1,NULL,pth1,NULL);
	pthread_create(&tid2,NULL,pth2,NULL);

	pthread_join(tid3,NULL);
	pthread_join(tid1,NULL);
	pthread_join(tid2,NULL);

    free(buf);
	pthread_cond_destroy(&cond1);//销毁条件变量
	pthread_mutex_destroy(&mutex1);//销毁互斥锁
	return 0;
}
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值