1、关于进程和线程重点讲义资料线程和进程1-线程和进程的区别:线程:是一种轻量级进程,线程存在于进程中。线程和进程一样都会被操作系统调度(时间片)通常线程指的是共享相同地址空间的多个任务。线程优势:线程运行时,相互切换效率高;线程之间共享数据很方便。进程拥有独立的运行空间,一个进程崩溃后,在保护模式下并不会影响其他的进程。一个进程中可以包含有多个线程,而一个线程只能包含在进程中。一个线程拥有自己独有的局部变量栈,但是没有独立的空间,一个进程中多个线程共同共享一块资源,因此当一个线程崩溃后此进程也会崩溃。因此多进程要比多线程健壮性要好,但是,多进程效率比较低。当需要并行操作和共享某些变量最好使用多线
2、程的模式。2关于线程的创建: int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void * (* routine)(void *), void *arg) 参数:thread, 线程的标识符(类似于进程的pid号) attr, 用于指定创建的线程的属性, 通常为NULL(不需要设置) routine, 函数指针,该函数就是线程主体 arg, 就是传递给函数的参数。返回值:成功返回0, 失败返回非负的错误号 int pthread_exit(void *value_ptr) 功能:只会导致当前线程的退出函数 参数
3、:就是传递的退出状态(指针) 通常使用: pthread_exit(0); 返回值:成功返回0, 失败返回非负的错误号 进程对已经退出的线程必须要做回收线程资源的操作(否则会产生僵尸线程) int pthread_join(pthread_t thread, void *value_ptr) 功能:阻塞等待回收退出的线程的资源 参数: thread, 就是指定要回收的线程资源 value_ptr, 就是接收到线程的退出状态。 返回值:成功返回0, 失败返回非负的错误号 3线程的控制: 互斥锁 :主要用来保护临界资源(可是变量,后者是代码段) 任何时刻最多只能有一个线程能访问该资源相关函数接口:
4、 int pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *attr) 功能:锁的初始化函数,即生成一把锁 参数: mutex, 是锁的标识符 attr, 设置锁的属性, 通常为NULL。 返回值:成功返回0, 失败返回非负的错误号 int pthread_mutex_lock(pthread_mutex_t *mutex) 功能:阻塞等待直到这把锁申请成功为止,对临界资源进行上锁操作 参数: mutex, 就是指定的一把锁 返回值:成功返回0, 失败返回非负的错误号 int pthread_mutex_unlock
5、(pthread_mutex_t *mutex) 功能:对临界资源进行解锁操作 参数: mutex, 就是指定要解开的锁 返回值:成功返回0, 失败返回非负的错误号 int pthread_mutex_destroy(pthread_mutex_t *mutex); 功能:销毁一把指定的锁 参数: mutex, 就是指定要销毁的锁 返回值:成功返回0, 失败返回非负的错误号注意: 为了避免死锁:在申请多把锁时,所有的线程都按照同样的顺序去申请。4线程之间的通信:int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_c
6、ondattr_t *restrict attr); 功能:初始化一个条件变量 参数:cond, 就是条件变量的标识符 attr, 通常为NULL即可 返回值:成功返回0, 失败返回非负的错误号 int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex); 功能:条件睡眠,直到被指定的条件唤醒为止 参数: cond, 就是指定睡眠 条件,将来被唤醒时也必须满足该条件 mutex, 该函数睡眠时必须提前加上一把锁 注意:该睡眠函数调用之前必须先加上一把锁,然后进入睡眠,然后该函数内部
7、将锁解开;当该函数被唤醒时,需要重新加上这把锁,如果发现这把锁被其他线程 占用,那么该函数的唤醒操作就失败了,继续睡眠。 返回值:成功返回0, 失败返回非负的错误号 int pthread_cond_signal(pthread_cond_t *cond); 功能:就是唤醒睡眠在cond条件上的线程,但是只能唤醒一个线程 参数: cond, 就是指定要唤醒的条件 返回值:成功返回0, 失败返回非负的错误号 int pthread_cond_broadcast(pthread_cond_t *cond); 功能:唤醒所有睡眠这个条件上的线程 int pthread_cond_destroy(pt
8、hread_cond_t *cond); 功能:就是销毁条件变量 5-处理消息队列 创建/打开消息队列对象 int msgget(key_t key, int flag); 参数:key, ftok()函数的返回值,用于确保多个进程操作同一个消息队列 flag, IPC_CREAT IPC_EXCL 和共享内存一样 返回值:成功返回消息队列的标识符, 失败-1 比如:msgget(key, IPC_CREAT|IPC_EXCL|0666); int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg); 参数:msgid,
9、msgget的返回值 msgp, 要发送的数据的指针,有指定的数据格式。 struct msgbuf long mtype; /* message type, must be 0 */ 数据类型,可以自定义; /* message data */ int a; char b; float f; ; msgsz, 发送的消息的正文的长度 = sizeof(struct msgbuf) - sizeof(long) msgflg,IPC_NOWAIT 以非阻塞方式发送消息,如果发送不成功,那么不会阻塞,立刻返回。 0(常用), 以阻塞方式发送消息,如果发送不成功,那么阻塞等待,直到发送成功为止。
10、返回值:成功0, 失败-1 ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg); 参数:msqid, msgget的返回值 msgp, 用于接收读取到的消息,固定格式,必须和发送的类型保持一致。 msgsz, 仍然要接收的消息的正文长度 msgtyp, 0, 表示读取第一条消息 0, 表示读取指定类型消息(最常用) 0, 接收消息队列中类型值不小于msgtyp的绝对值且类型值又最小的消息 msgflg, IPC_NOWAIT 以非阻塞方式接收消息,如果接收不成功,那么不会阻塞,立刻返回。 0(
11、常用), 以阻塞方式接收消息,如果接收不成功,那么阻塞等待,直到接收成功为止。 返回值:实际接收到的消息的正文的字节个数 , 失败-1. 消息队列的控制函数 int msgctl ( int msgqid, int cmd, struct msqid_ds *buf ); 参数: msgqid, 就是控制的消息队列 cmd : IPC_STAT (获取对象属性), 属性保存在第三个参数上 IPC_SET (设置对象属性),第三个参数保存的是要修改的属性 IPC_RMID (删除对象), 此时第三个参数为NULL即可 返回值:返回值和cmd有关系, 失败-1.6共享内存 共享内存:是一种最为高效
12、的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝 注意:由于多个进程共享一段内存,因此也需要依靠某种同步机制来控制对它的写操作,如互斥锁和信号量等 #include key_t ftok(const char *pathname, int proj_id); 功能:就是生成key值。 参数:pathname, 任意一个存在的路径都可以 proj_id, 只要低8位(二进制)不全为0的数都可以 返回值:成功key值, 失败-1. 比如:key = ftok(., a); 创建/打开共享内存:创建于当前系统上,保存到系统关闭为止。 int shmget(key_t key, int
13、size, int shmflg); 参数:key, 用于唯一的标识一块共享内存,将来其他进程需要使用一样的key值。 IPC_PRIVATE,表示该物理空间只能自己用,无法和其他进程共享。 ftok()函数的返回值 size, 要申请的共享内存物理空间, /usr/inclue/linux/shm.h中包含限制 shmflg, IPC_CREAT IPC_EXCLIPC_CREAT 如果共享内存不存在,则创建一个共享内存,否则打开操作。IPC_EXCL 只有在共享内存不存在的时候,新的共享内存才建立,否则就产生错误。 返回值:就是共享内存的标识符(大于0),失败-1. 比如:key = ft
14、ok(., a);int shmid;shmid = shmget(key, 512, IPC_CREAT|IPC_EXCL|0666); 映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问 void *shmat(int shmid, const void *shmaddr, int shmflg); 参数:shmid, 就是shmget的返回值 shmaddr, 用于指定共享内存映射到当前进程的那个起始地址上 如果为NULL, 那么系统帮助进程分配(最常用) 如果非NULL, 那么就是自己指定(必须保证这块空间没有被使用) shmflg, 指定进程对该内存区域的读写权限 如果为S
15、HM_RDONLY ,那么当前进程只读 如果为0, 那么当前进程可读可写(最常用) 返回值:返回当前进程和共享内存映射后的起始地址 失败返回的为(void*)(-1)撤销共享内存映射 int shmdt(const void *shmaddr); 参数:shmaddr, 就是shmat的返回值 返回值:成功0, 失败-1 操作共享内存对象 int shmctl(int shmid, int cmd, struct shmid_ds *buf); / 获取共享内存的状态,并把相关的属性赋值给buf 功能:控制共享内存对象 参数: shmid, shmget的返回值 cmd : IPC_STAT
16、(获取对象属性), 属性保存在第三个参数上 IPC_SET (设置对象属性),第三个参数保存的是要修改的属性 IPC_RMID (删除对象), 此时第三个参数为NULL即可 返回值:返回值和cmd有关系, 失败-1. 比如:shmctl(shmid, IPC_RMID, NULL); 创建/打开信号量集合 int semget(key_t key, int nsems, int semflg); 参数: key, ftok()返回值 nsems, 指定的信号量集合中的信号量个数 semflg, IPC_CREAT IPC_EXCL IPC_CREAT 如果共享内存不存在,则创建一个共享内存,否
17、则打开操作。 IPC_EXCL 只有在共享内存不存在的时候,新的共享内存才建立,否则就产生错误。 返回值:返回该集合的标识符,失败-1. int semctl(int semid, int semnum, int cmd, .); 功能:信号量集合的控制 参数:semid, 指定要操作的集合 semnum, 要操作的信号量的编号,编号从0开始 cmd,GETVAL:获取信号灯的值, 返回值是获得值。 比如:value = semctl(semid, 0, GETVAL); SETVAL:设置信号灯的值,需要用到第四个参数。因此在设置信号灯的值时应该及时的设置共用体semun的值 第四个参数类型
18、如下: 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) */ ; int semop ( int semid, struct sembuf *opsptr, unsigned nops); 功能:就是对信号量集合中
19、的信号量进行PV操作 参数: semid, 指定要操作的集合 opsptr, struct sembuf unsigned short sem_num; /* semaphore number */ short sem_op; /* semaphore operation */ short sem_flg; /* operation flags */ ; 成员分析: sem_num 表示要操作的信号量的编号 sem_op, 表示进行P或者V操作, 比如:sem_op = 10(+10) sem_op = -10(-10) sem_op = 0, 那么semop函数会等到该信号量的值变为0为止。
20、 sem_flg, 0(最常用的),表示semop函数的操作是阻塞的,直到成功为止。 IPC_NOWAIT,表示semop函数的操作是非阻塞的,如果操作没有成功,立刻返回。 SEM_UNDO(不常用),设置只对当前进程有效,不会保存到系统的信号量集合中。 nops, 调用一次semop要操作的信号量的个数 返回值:成功0, 失败-1 7-linux 管道、消息队列、共享内存的对比 管道的优点是不需要加锁,缺点是默认缓冲区太小,只有4K,同时只适合父子进程间通信,而且一个管道只适合单向通信,如果要双向通信需要建立两个。而且不适合多个子进程,因为消息会乱,它的发送接收机制是用read/write这
21、种适用流的,缺点是数据本身没有边界,需要应用程序自己解释,而一般消息大多是一个固定长的消息头,和一个变长的消息体,一个子进程从管道read到消息头后,消息体可能被别的子进程接收到 消息队列也不要加锁,默认缓冲区和单消息上限都要大一些,在我的suse10上是64K,它并不局限于父子进程间通信,只要一个相同的key,就可以让不同的进程定位到同一个消息队列上,它也可以用来给双向通信,不过稍微加个标识,可以通过消息中的type进行区分,比如一个任务分派进程,创建了若干个执行子进程,不管是父进程发送分派任务的消息,还是子进程发送任务执行的消息,都将type设置为目标进程的pid,因为msgrcv可以指定
22、只接收消息类型为type的消息,这样就实现了子进程只接收自己的任务,父进程只接收任务结果 共享内存的几乎可以认为没有上限,它也是不局限与父子进程,采用跟消息队列类似的定位方式,因为内存是共享的,不存在任何单向的限制,最大的问题就是需要应用程序自己做互斥,有如下几种方案1 只适用两个进程共享,在内存中放一个标志位,一定要声明为volatile,大家基于标志位来互斥,例如为0时第一个可以写,第二个就等待,为1时第一个等待,第二个可以写/读2 也只适用两个进程,是用信号,大家等待不同的信号,第一个写完了发送信号2,等待信号1,第二个等待信号2,收到后读取/写入完,发送信号1,它不是用更多进程是因为虽
23、然父进程可以向不同子进程分别发送信号,但是子进程收到信号会同时访问共享内存,产生不同子进程间的竞态条件,如果用多块共享内存,又存在子进程发送结果通知信号时,父进程收到信号后,不知道是谁发送,也意味着不知道该访问哪块共享内存,即使子进程发送不同的结果通知信号,因为等待信号的一定是阻塞的,如果某个子进程意外终止,父进程将永远阻塞下去,而不能超时处理3 采用信号量或者msgctl自己的加锁、解锁功能,不过后者只适用于linux总结: 管道是最弱的,只适合有限场景; 消息队列能适合大部分场景,缺点是默认缓冲也比较小,不过这个可以调整,前提是你有管理员权限; 共享内存是最强大的,只是要做互斥 进程间通信
24、:1.管道(pipe)及有名管道(named pipe):管道可用于具有亲缘关系的父子进程间的通信,有名管道除了具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。2.信号(signal):信号是在软件层次上对中断机制的一种模拟,它是比较复杂的通信方式,用于通知进程有某事件发生,一个进程收到一个信号与处理器收到一个中断请求效果上可以说是一致的。3.消息队列(message queue):消息队列是消息的链接表,它克服了上两种通信方式中信号量有限的缺点,具有写权限得进程可以按照一定得规则向消息队列中添加新信息;对消息队列有读权限得进程则可以从消息队列中读取信息。4.共享内存(shared m
25、emory):可以说这是最有用的进程间通信方式。它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据得更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。5.信号量(semaphore):主要作为进程之间及同一种进程的不同线程之间得同步和互斥手段。6.套接字(socket):这是一种更为一般得进程间通信机制,它可用于网络中不同机器之间的进程间通信,应用非常广泛 while (1) pthread_mutex_lock(&mutex_sms); pthread_cond_wait(&cond_sms, &mutex_sms); /等待发送短信条件 buf =
26、dev_sms_cmd; printf (pthread_sms is wake up! dev_sms_cmd = %dn, buf); pthread_mutex_unlock(&mutex_sms); switch(buf) case SMS_STRNO1 | SMS_TEM: send_message(dev_sms_fd, SMS_MSG_TEM1); break; case SMS_STRNO1 | SMS_BRE: send_message(dev_sms_fd, SMS_MSG_BRE1); break; case SMS_STRNO1 | SMS_ILL: send_mess
27、age(dev_sms_fd, SMS_MSG_ILL1); break; case SMS_STRNO1 | SMS_HUM: send_message(dev_sms_fd, SMS_MSG_HUM1); break; case SMS_STRNO2 | SMS_TEM: send_message(dev_sms_fd, SMS_MSG_TEM2); break; case SMS_STRNO2 | SMS_BRE: send_message(dev_sms_fd, SMS_MSG_BRE2); break; case SMS_STRNO2 | SMS_ILL: send_message(
28、dev_sms_fd, SMS_MSG_ILL2); break; case SMS_STRNO2 | SMS_HUM: send_message(dev_sms_fd, SMS_MSG_HUM2); break; if (flag) pthread_mutex_lock (&mutex_sms); if (sto_no = 1) dev_sms_cmd = SMS_TEM |SMS_STRNO1; else dev_sms_cmd = SMS_TEM |SMS_STRNO2; pthread_mutex_unlock (&mutex_sms); pthread_cond_signal (&cond_sms); flag = 0;
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1