1. 多线程开发需要注意哪些?
    1、明确目的,为什么要使用多线程?如果是由于单线程读写或者网络访问(例如HTTP访问互联网)的瓶颈,可以考虑使用线程池。如果是对不同的资源(例如SOCKET连接)进行管理,可以考虑多个线程。
    2、线程使用中要注意,如何控制线程的调度和阻塞,例如利用事件的触发来控制线程的调度和阻塞,也有用消息来控制的。
    3、线程中如果用到公共资源,一定要考虑公共资源的线程安全性。一般用LOCK锁机制来控制线程安全性。一定要保证不要有死锁机制。
    4、合理使用sleep,何时Sleep,Sleep的大小要根据具体项目,做出合理安排。一般原则非阻塞状态下每个循环都要有SLeep,这样保证减少线程对CPU的抢夺。每次线程的就绪和激活都会占用一定得资源,如果线程体如果有多个循环,多处使用SLEEP将导致性能的下降。
    5、线程的终止一般要使线程体在完成一件工作的情况下终止,一般不要直接使用抛出线程异常的方式终止线程。
    6、线程的优先级一定根据程序的需要要有个整体的规划。

2.进程间的通信方式,及特点
共享内存
1、特点
a、由内核维护一个共享的内存区域,其它进程把自己虚拟地址映射到这块内存,多个进程之间就可以共享这块内存了。
b、这种进程间通信的好处是不需要信息复制,是进程间通信最快的一种方式。
c、但这种通信方式会面临同步的问题,需要与其它通信方式配合,最合适的就是信号

例子:
使用了【共享内存+信号】的组合来实现服务器进程与客户进程间的通信。
服务端

#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <signal.h>
#include <unistd.h>

char* buf = NULL;
void sigint(int num)
{
	printf("\r接收到数据:%s\n",buf);
	printf(">");
	fflush(stdout);
}

int main()
{
	signal(SIGINT,sigint);
	
	int pid = 0;
	printf("我是进程%d\n",getpid());
	printf("与我通信的进程是:");
	scanf("%d",&pid);
	getchar();

	// 创建共享内存
	int shmid = shmget(key,4096,IPC_CREAT|0744);
	if(0 > shmid)
	{
		perror("shmget");
		return -1;
	}
	
	// 加载共享内存
	buf = shmat(shmid,NULL,SHM_RND);

	while(1)
	{
		printf(">");
		gets(buf);
		kill(pid,SIGINT);
	}

	if(shmdt(buf))
	{
		perror("shmdt");
	}
}

客户端

#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <signal.h>
#include <unistd.h>

char* buf = NULL;
void sigint(int num)
{
	printf("\r接收到数据:%s\n",buf);
	printf(">");
	fflush(stdout);
}

int main()
{
	signal(SIGINT,sigint);
	key_t key = 39242235;
	
	int pid = 0;
	printf("我是进程%d\n",getpid());
	printf("与我通信的进程是:");
	scanf("%d",&pid);
	getchar();

	// 创建共享内存
	int shmid = shmget(key,4096,0);
	if(0 > shmid)
	{
		perror("shmget");
		return -1;
	}
	
	// 加载共享内存
	buf = shmat(shmid,NULL,SHM_RND);

	while(1)
	{
		printf(">");
		gets(buf);
		kill(pid,SIGINT);
	}

	if(shmdt(buf))
	{
		perror("shmdt");
}

2.消息队列:
是消息的链接表,存放在 内核中。一个消息队列由一个标识符(即队列ID)来标识。

1、特点
消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。
消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。
消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取
2.示例:

server.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/msg.h>

// 用于创建一个唯一的key
#define MSG_FILE "."

// 消息结构
struct msg_form {
    long mtype;
    char mtext[256];
};

int main()
{
    long msqid;
    key_t key;
    struct msg_form msg;
    
    // 获取key值
    if((key = ftok(MSG_FILE,'z')) < 0)
    {
        perror("ftok error");
        exit(1);
    }

    // 打印key值
    printf("Message Queue - Server key is: %d.\n", key);

    // 创建消息队列
    if ((msqid = msgget(key, IPC_CREAT|0777)) == -1)
    {
        perror("msgget error");
        exit(1);
    }

    // 打印消息队列ID及进程ID
    printf("My msqid is: %ld.\n", msqid);
    printf("My pid is: %d.\n", getpid());

    // 循环读取消息
    for(;;) 
    {
        msgrcv(msqid, &msg, 256, 888, 0);// 返回类型为888的第一个消息
        printf("Server: receive msg.mtext is: %s.\n", msg.mtext);
        printf("Server: receive msg.mtype is: %ld.\n", msg.mtype);

        msg.mtype = 999; // 客户端接收的消息类型
        sprintf(msg.mtext, "hello, I'm server %d", getpid());
        msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
    }
    return 0;
}
client.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/msg.h>

// 用于创建一个唯一的key
#define MSG_FILE "."

// 消息结构
struct msg_form {
    long mtype;
    char mtext[256];
};

int main()
{
    long msqid;
    key_t key;
    struct msg_form msg;

    // 获取key值
    if ((key = ftok(MSG_FILE, 'z')) < 0) 
    {
        perror("ftok error");
        exit(1);
    }

    // 打印key值
    printf("Message Queue - Client key is: %d.\n", key);

    // 打开消息队列 
    if ((msqid = msgget(key, IPC_CREAT|0777)) == -1) 
    {
        perror("msgget error");
        exit(1);
    }

    // 打印消息队列ID及进程ID
    printf("My msqid is: %ld.\n", msqid);
    printf("My pid is: %d.\n", getpid());

    // 添加消息,类型为888
    msg.mtype = 888;
    sprintf(msg.mtext, "hello, I'm client %d", getpid());
    msgsnd(msqid, &msg, sizeof(msg.mtext), 0);

    // 读取类型为999的消息
    msgrcv(msqid, &msg, 256, 999, 0);
    printf("Client: receive msg.mtext is: %s.\n", msg.mtext);
    printf("Client: receive msg.mtype is: %ld.\n", msg.mtype);
    return 0;
}

3.信号量:
信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。

信号量是一个特殊的变量,程序对其访问都是原子操作,且只允许对它进行等待(即P(信号变量))和发送(即V(信号变量))信息操作。

1、特点
a、信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。

b、信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。

c、每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。

d、支持信号量组。

2.定义PV:
P操作:货物个数减1,减过之后,货物个数大于等于0,说明已经拿到货物,线程继续。否者线程阻塞。
V操作:货物个数加1,加过之后,货物个数小于等于容量,说明添加成功,线程继续。否者线程阻塞。

信号量:
0≤ 信号量≤容量 ,取值 表示当前可以使用的货物;
信号量<0 , 取值 表示当前等待使用货物的线程;
信号量>容量 , 信号量-容量 表示当前等待添加货物的线程

3.例子:
//

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

// 联合体,用于semctl初始化
union semun
{
    int              val; /*for SETVAL*/
    struct semid_ds *buf;
    unsigned short  *array;
};

// 初始化信号量
int init_sem(int sem_id, int value)
{
    union semun tmp;
    tmp.val = value;
    if(semctl(sem_id, 0, SETVAL, tmp) == -1)//0 :信号量集数组上的下标,表示某一个信号量
    {
        perror("Init Semaphore Error");
        return -1;
    }
    return 0;
}

// P(sv)操作:
//    如果sv的值大于零,就给它减1;
//    如果它的值为零,就挂起该进程的执行

int sem_p(int sem_id)
{
    struct sembuf sbuf;
    sbuf.sem_num = 0; /*序号*/
    sbuf.sem_op = -1; /*P操作*/
    sbuf.sem_flg = SEM_UNDO; 
    /*通常为SEM_UNDO,使操作系统跟踪信号,
    并在进程没有释放该信号量而终止时,操作系统释放信号量*/

    if(semop(sem_id, &sbuf, 1) == -1)
    {
        perror("P operation Error");
        return -1;
    }
    return 0;
}

// V(sv)操作:
//    如果有其他进程因等待sv而被挂起,就让它恢复运行,唤醒它们
//    如果没有进程因等待sv而挂起,就给它加1.
int sem_v(int sem_id)
{
    struct sembuf sbuf;
    sbuf.sem_num = 0; /*序号*/
    sbuf.sem_op = 1;  /*V操作*/
    sbuf.sem_flg = SEM_UNDO;

    if(semop(sem_id, &sbuf, 1) == -1)
    {
        perror("V operation Error");
        return -1;
    }
    return 0;
}

// 删除信号量集
int del_sem(int sem_id)
{
    union semun tmp;
    if(semctl(sem_id, 0, IPC_RMID, tmp) == -1)//IPC_RMID删除信号量 
    {
        perror("Delete Semaphore Error");
        return -1;
    }
    return 0;
}


int main()
{
    int sem_id;  // 信号量集ID
    key_t key;  
    pid_t pid;

    // 获取key值
    if((key = ftok(".", 'z')) < 0)
    {
        perror("ftok error");
        exit(1);
    }

    // 创建信号量集,其中只有一个信号量
    if((sem_id = semget(key, 1, IPC_CREAT|0666)) == -1)
    {
        perror("semget error");
        exit(1);
    }

    // 初始化:初值设为0资源被占用
    init_sem(sem_id, 0);

    if((pid = fork()) == -1)
    {
        perror("Fork Error");
    }
    else if(pid == 0) /*子进程*/ 
    {
        sleep(2);
        printf("Process child: pid=%d\n", getpid());
        printf("children give sem:\n");
        sem_v(sem_id);  /*释放资源*/
    }
    else  /*父进程*/
    {
        printf("father wait sem:\n");
        sem_p(sem_id);   /*等待资源*/
        printf("Process father: pid=%d\n", getpid());
        printf("father lend sem:\n");
        sem_v(sem_id);   /*释放资源*/
        del_sem(sem_id); /*删除信号量集*/
    }
    return 0;
}

4.管道
a、它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端。
b、它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)。
c、它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于 其他任何文件系统,并且只存在于内存中。

2、原型:
1 #include <unistd.h>
2 int pipe(int fd[2]); // 返回值:若成功返回0,失败返回-1
1
2
当一个管道建立时,它会创建两个文件描述符:fd[0]为读而打开,fd[1]为写而打开。如下图:

若要数据流从父进程流向子进程,则关闭父进程的读端(fd[0])与子进程的写端(fd[1]);反之,则可以使数据流从子进程流向父进程。

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

int main()
{
	char buff[255] = {};
	int fd[2];//文件描述符
	pid_t pid;

	// 返回值:若成功返回0,失败返回-1
	if(pipe(fd) < 0)//创建管道
	{
		perror("pipe");
		return -1;
	}

	//赋值运算符比比较运算符优先级低
	if( (pid = fork()) < 0)//创建子进程
	{
		perror("fork");
		return -1;
	}
	else if(pid == 0)//子进程
	{
		printf("\npid:%d\n",getpid());
		close(fd[1]);
		while(1)
		{
			sleep(1);			
			read(fd[0],buff,20);
			printf("%s\n",buff);
			bzero(0,sizeof(buff));
			getchar();
		}
	}	
	else//父进程
	{
		close(fd[0]);
		write(fd[1],"hahaha",12);
	}
	return 0;
}