1. 消息队列
传送有格式的消息流
多进程网状交叉通信,实现大规模数据通信
使用内核中的链表(实现机制)
- posix的消息队列和系统V消息队列的区别
1、一般来说posix的接口要比systemV的简单,但是systemV的可已移植性更好几乎所有的unix系统都支持。
2、对posix消息队列的读总是返回最高优先级的最早消息,对systemV消息队列的读则可以返回
任意指定优先级的消息。
3、当往一个空队列放置一个消息时,posix消息队列允许产生一个信号或者启动一个线程,systemV消息队列则不提供类似的机制。
消息队列是随内核持续的,所以会产生的现象是:消息队列随内核开始而开始,随内核结束而结束。
2. 线程
-
什么是线程
线程是进程的一个实体,它是程序运行的最小单位。 -
为什么要学习线程(线程的作用)
1、线程是进程的一个实体,它是程序运行的最小单位,它比进程要消耗更少的资源
2、能共享地址空间(进程的地址空间)(堆栈:程序栈) -
线程由哪些组成
1、指令指针(指向当前执行的命令)
2、一个栈(函数栈)
3、寄存器的集合(状态寄存器:一部分正在运行中的处理器的状态)
4、一个私有的数据区 -
线程的特点
1、线程切换的开销很低(因为线程切换的实质是函数切换)
2、线程的通信机制简单(由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,即共享进程的地址空间) -
多线程切换的实质:函数切换
线程并不是操作系统中内核所提供的而是由线程库来提供libpthread.a/.so
线程的调用离不开ios的支持
3. 创建线程
- int pthread_create()
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
pthread_t *thread:指向线程的指针;
const pthread_attr_t *attr:创建线程时的属性,一般是NULL,即无属性创建;
void *(*start_routine) (void *):线程运行的实体函数指针;
void *arg:线程的参数;
注意:线程是进程的一个实体,一旦主进程运行结束,线程就会被回收。 - 代码如下:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
void *func(void *arg) //次线程,子线程
{
while(1)
{
printf("This is a thread\n");
sleep(2);
}
}
int main()
{
pthread_t id;
int ret = pthread_create(&id, NULL, func, NULL);
if(0 != ret)
{
perror("pthread_create error!");
exit(0);
}
while(1)
{
printf("main thread!\n");
sleep(2);
}
pause(); //pause()表示挂起子线程;或者用while(1);
return 0;
}
运行结果如下:可以看出主线程和子线程是竞争关系
- 当次线程函数的参数是int型时,代码如下
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
void *func(void *arg)
{
int *num = (int *)arg;
printf("This is a subthread:%d\n", *num);
sleep(2);
}
int main()
{
int i = 5;
pthread_t id;
int ret = pthread_create(&id, NULL, func, (void *) &i);
if(0 != ret)
{
perror("pthread_create error!");
exit(0);
}
while(1)
{
printf("main thread!\n");
sleep(2);
}
pause(); //while(1);
return 0;
}
运行结果如下图:因为没有while(1)循环,子线程只输出了一次,但是主线程输出的时候,子线程并没有结束,只是处于挂起状态,仍然消耗资源。当Ctrl+c结束进程时,子线程的资源才会被回收。
- 当次线程函数的参数为结构体时,代码如下:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
struct demo
{
int id;
char name[1024];
};
void *func(void *arg)
{
struct demo *ptr = (struct demo *)arg;
printf("subthread id:%d, name:%s\n", ptr->id, ptr->name);
sleep(2);
}
int main()
{
struct demo demo1 = {2021, "zhang"};
pthread_t id;
int ret = pthread_create(&id, NULL, func, (void *)&demo1);
if(0 != ret)
{
perror("pthread_create error!");
exit(0);
}
while(1)
{
printf("main thread!\n");
sleep(2);
}
pause(); //while(1);
return 0;
}
运行结果如下:
4. 线程等待
pthread_join()
int pthread_join(pthread_t thread, void **retval);
pthread_t thread:需要等待的线程
void **retval:线程退出的状态
这是一个线程阻塞函数,调用它的进程将一直等待到线程执行完毕为止,而且当函数返回时,线程的资源将被收回。
线程等待的目的:
1、保证线程的退出顺序:保证一个线程退出并且回收资源后允许下一个进程退出
2、回收线程退出时的资源情况:保证当前线程退出后,创建的新线程不会复用刚才退出线程的地址空间
3、获得新线程退出时的结果是否正确退出返回值
作业:创建两个次线程,两个次线程分别向同一个文件写“hello”“world\n”,和"hhhhh"“wwwww\n”
5. 线程退出
- 被动退出:
int pthread_cancel(pthread_thread);
(1)功能
当次线程是死循环时,可以调动这个函数主动取消该线程。
(2)返回值
成功返回0,失败返回非零错误号。发送终止信号给thread线程,如果成功则返回0,否则为非0值。发送成功并不意味着thread会终止。
(3)参数
thread:要取消线程的TID。
当不发生系统调用时,线程不会结束。
代码如下:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define MAX_SIZE 1024
void *thread1(void *arg) //ci xian cheng
{
while(1)
{
printf("hi\n");
sleep(1);
}
}
int main() //main han shu xia de shi zi xian cheng
{
pthread_t id1;
if(pthread_create(&id1, NULL, thread1, NULL) != 0)
{
perror("pthread_create error!");
exit(0);
}
sleep(3);
pthread_cancel(id1);
pause();
return 0;
}
如下图,当次线程发生printf()这一系统调用时,可以用pthread_cancel()函数取消;但是将printf()注释掉之后(即没有发生系统调用时),线程不会结束。(在次线程里定义int i,并用i++;此时,i++不算是系统调用)
- 主动退出:
void pthread_exit(void *retval);
1、功能:线程调用这个函数时,可以主动退出(终止)。
它和exit()很像,exit()是终止整个程序,而pthread_exit是终止次线程。
如果在次线程里面调用错误,调用的是exit,整个线程就终止了。
2、返回值
成功返回值为0,失败返回非0
3、参数
retval:线程结束的返回值
如果返回值很多时,就会封装成一个结构体,返回结构体变量的地址即可。
正确代码如下:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define MAX_SIZE 1024
int num = 5;
void *thread1(void *arg) //ci xian cheng
{
while(1)
{
printf("hi\n");
sleep(1);
//pthread_exit(&num);
pthread_exit(NULL);
}
}
int main() //main han shu xia de shi zi xian cheng
{
//int *num;
pthread_t id1;
if(pthread_create(&id1, NULL, thread1, NULL) != 0)
{
perror("pthread_create error!");
exit(0);
}
sleep(3);
pthread_cancel(id1);
pthread_join(id1, (void *)&num);
printf("thread is exit! %d\n", *((int *)num));
pause();
return 0;
}
运行结果如下:
-
return ()退出线程(但不会弹栈,退出后数据不会弹出栈)
举例:return (void *)0;
-
注册线程退出处理函数
void pthread_cleanup_push(void (*routine)(void *), void *arg);
void pthread_cleanup_pop(int execute);
void(*rtn)(void *): 线程清理函数
这两个函数是成对出现。
弹栈线程退出处理函数的几种条件:
pthread_cleanup_pop(!0)
:主动弹栈;(pthread_cleanup_pop(0)
表示不会弹栈)
如果线程是被别人调用pthread_cancel
取消的,也会弹栈;
如果调用pthread_exit
函数也会主动弹栈;
==注意:==return退出的话是不会自动弹栈的,想要自动弹栈用pthread_cleanup_pop(!0)
6. 关于进程和线程的不同点
1、进程关心进程间的通信;线程关心共享的数据资源(线程的通信靠全局变量)
2、每个进程都是单独的地址空间;多个线程可以共享一个进程的地址空间,线程是进程的一个实体。
7. 线程的状态
1、可结合态:这种状态下的线程是能被其他进程回收资源或被杀死的
2、可分离态:不能被其他进程回收资源或被杀死的,它存储的资源在它终止时由系统自动回收
默认情况下,线程是可结合态
- 线程分离函数
pthread_detach()
1、功能:如果次线程不希望别人调用pthread_join()函数来回收,而是希望自己在运行结束时,自动回收资源调用pthread_detach();
将pthread_detach()中的线程变成分离态
2、返回值:成功返回1;错误返回一个非零值
3、参数:thread:要分离的那个次线程的TID
8. 进程和线程的区别
1、进程——资源分配的最小单位,线程——资源调度的最小单位
2、每个进程有独立的地址空间,多个线程共享进程的地址空间,线程间的切换比进程间的切换开销要小。
3、线程的调度必须通过频繁地加锁来保持线程的同步,影响线程的并发性能。
4、进程比线程更加健康,多进程之间相互独立,一个进程的异常对其他进程不影响,而一个线程的崩溃可能影响其他线程或者整个程序。
5、通信方式不同:线程间的通信方便(小数据量),同一进程下共享全局变量还有静态变量等数据;而进程间的通信需要以通信的方式(IPC)进行。不过如何处理好同步和互斥是编写多线程程序的难点。
6、多线程代码结构比多进程代码结构简单易读。