13.1.阻塞式和非阻塞式IO
(1)常见的阻塞式函数有wait+pause+sleep等函数;read或write某些IO设备文件(譬如串口/鼠标/键盘)时会产生阻塞现象;阻塞式的好处是内核操作系统实现简单并有利于操作系统充分发挥CPU的全部性能;当某个线程同时操作多路IO(譬如同时操作鼠标和键盘)时就必须实现非阻塞式IO,我们可通过O_NONBLOCK和fcntl实现非阻塞IO访问。
(2)键盘是标准输入设备stdin(对应的fd为0);鼠标对应的是/dev/input目录下面的/mouse0/mouse1/mouse2(可使用cat读取相应的mouse文件然后移动鼠标确定具体是哪个mouse);如果在程序中同时读取键盘和鼠标,则用户必须按照程序设计的先读键盘后读鼠标的顺序去操作先操作键盘后操作鼠标,否则会导致鼠标和键盘动作无法识别,这就是阻塞式IO的困境。
(3)并发式IO的解决方案有非阻塞式IO+多路复用IO+异步通知(异步IO)这3种方式;多路IO复用(IO_multiplexing)一般用在多路非阻塞式IO上面;select和poll该两个函数用来实现IO多路复用的思想相同,只是该两个函数的外部特征不同(历史原因造成);IO多路复用的原理即外部阻塞式(select和poll函数本身是阻塞式的)而内部非阻塞式自动轮询多路阻塞式IO(select和poll两个函数的内部实现是非阻塞式的)。
(4)几乎可以认为异步IO就是操作系统用软件实现的1套中断响应系统;异步IO的工作原理是由我们当前进程注册1个异步IO事件(使用signal注册1个信号SIGIO的处理函数),然后当前进程可以正常处理自己的事情,当异步事件发生后当前进程会收到1个SIGIO信号从而执行绑定的处理函数去处理这个异步事件;涉及的函数有fcntl(F_GETFL+F_SETFL+O_ASYNC+F_SETOWN)+signal/sigaction(SIGIO)。
(5)存储映射IO;mmap函数(把某个文件与某段内存进行映射对应),主要应用在LCD显示和IPC之共享内存/两个进程之间的共享内存通讯;存储映射IO的特点=共享而不是复制,减少内存操作+处理大文件时效率高,小文件不划算。
13.2.进程和线程的对比
(1)使用进程技术的优势=通过CPU时分复用,单核心CPU可以通过多进程实现宏观上的并行+使用多进程实现多任务系统需求(多任务的需求是客观的);进程技术的劣势=进程间切换开销大+进程间通信麻烦而且效率低;线程技术的优势=线程技术保留了进程技术实现多任务的特性+线程的改进就是在线程间切换和线程间通信上提升了效率+多线程在多核心CPU上面更有优势。
(2)linux中的线程=1种轻量级进程+线程是参与内核调度的最小单元+1个进程中可以有多个线程;线程技术的优势=线程可像进程一样可被OS调度+同一进程的多个线程之间很容易高效率通信+多线程技术在多核心CPU(对称多处理器架构SMP)架构下能使程序效率最大化。
(3)线程常见函数;线程创建与回收-pthread_create(主线程用来创造子线程)+pthread_join(主线程用来等待(阻塞)回收子线程)+pthread_detach(主线程用来分离子线程,分离后主线程不必再去回收子线程);线程取消-pthread_cancel(一般都是主线程调用该函数去取消(让它赶紧死)子线程)+pthread_setcancelstate(子线程设置自己是否允许被取消)+pthread_setcanceltype(设置子线程被取消的类型);线程函数退出相关-pthread_exit与return(退出)+pthread_cleanup_push(子线程已上锁正在执行代码时忽然被主线程取消为保证锁的安全释放而压栈解锁函数)+pthread_cleanup_pop(子线程已上锁正在执行代码时没有被主线程取消而出栈解锁函数决定解锁函数是否执行)(见图1);获取线程id-pthread_self。
13.3.线程同步之信号量+互斥锁+条件变量
(1)线程同步是主线程和子线程之间需要相互配合;平时子线程需要循环阻塞在某个地方,然后主线程正常工作期间若满足某种条件就可发信号激活子线程执行,子线程执行完后再次循环阻塞等待被主线程激活;信号量可实现线程同步。
(2)用户从终端输入任意字符然后统计个数显示,输入end则结束;使用多线程实现即主线程获取用户输入并判断是否退出,子线程负责计数并打印输出。
(3)互斥锁又叫互斥量(mutex);互斥锁涉及到的相关函数pthread_mutex_init+pthread_mutex_destroy+pthread_mutex_lock+pthread_mutex_unlock;可以认为互斥锁是1种特殊的信号量,严格来说互斥锁是只有0和1两种状态的信号量;互斥锁主要用来实现关键代码段的保护;若man_3_pthread_mutex_init时提示找不到函数,说明没有安装pthread相关的man手册,可使用sudo_apt-get_install_manpages-posix-dev来下载该手册。
(4)条件变量是线程所特有的;首先我们全局定义了1个条件,然后当某个线程不满足该条件的时候会导致该线程阻塞等待该条件,然后另外1个线程满足某种条件后会发送条件到阻塞等待该条件的线程从而激活它;条件变量类似进程同步中的异步IO。
(5)条件变量涉及到的相关函数pthread_cond_init+pthread_cond_destroy+pthread_cond_wait+pthread_cond_signal/pthread_cond_broadcast。
13.keyboard_mouse_io
/*
* 公司:XXXX
* 作者:Rston
* 博客:http://blog.csdn.net/rston
* GitHub:https://github.com/rston
* 项目:高级IO和多线程和线程同步
* 功能:阻塞式IO+非阻塞式IO+多路复用IO+异步IO。
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/select.h>
#include <sys/time.h>
#include <poll.h>
#include <signal.h>
typedef void (*sighandler_t)(int);
int mousefd = -1;
#if 1
// SIGIO信号捕获函数
void signal_func(int sig)
{
char buf[1024] = {0};
int ret = -1;
if (sig != SIGIO)
return;
ret = read(mousefd, buf, 8);
if (ret > 0)
{
printf("The coment of read mouse is [%s].\n", buf);
}
}
#endif
int main(int argc, char **argv)
{
int fd = -1, ret = -1, flag = -1, i = 0;
char buf[1024];
fd_set rfds;
struct timeval tv;
struct pollfd pfds[2] = {0};
// 同时读取键盘设备文件和鼠标设备文件演示阻塞式IO的困境
#if 0
// 读取鼠标设备文件
fd = open("/dev/input/mouse0", O_RDONLY);
if (fd < 0)
{
perror("open error");
exit(-1);
}
memset(buf, 0, sizeof(buf));
printf("before read mouse.\n");
read(fd, buf, 8);
printf("The coment of read mouse is [%s].\n", buf);
// 读取键盘设备文件
memset(buf, 0, sizeof(buf));
printf("before read keyboard.\n");
read(0, buf, 8);
printf("The coment of read keyboard is [%s].\n", buf);
#endif
// 同时读取键盘设备文件和鼠标设备文件演示非阻塞式IO解决方案
#if 0
// 以非阻塞的方式打开mouse设备文件
fd = open("/dev/input/mouse0", O_RDONLY | O_NONBLOCK);
if (fd < 0)
{
perror("open error");
exit(-1);
}
// 把默认输入stdin变成非阻塞式的
flag = fcntl(0, F_GETFL); // 获取原来的Flag
flag |= O_NONBLOCK; // 添加非阻塞属性
fcntl(0, F_SETFL, flag); // 更新Flag
while (1)
{
memset(buf, 0, sizeof(buf));
ret = read(fd, buf, 8);
if (ret > 0)
{
printf("The coment of read mouse is [%s].\n", buf);
}
// 读取键盘设备文件
memset(buf, 0, sizeof(buf));
ret = read(0, buf, 8);
if (ret > 0)
{
printf("The coment of read keyboard is [%s].\n", buf);
}
}
#endif
// 通过select函数实现IO多路复用同时读取键盘设备文件和鼠标设备文件
#if 0
fd = open("/dev/input/mouse0", O_RDONLY);
if (fd < 0)
{
perror("open error");
exit(-1);
}
// 将fd和0两个文件添加到rfds中
FD_ZERO(&rfds);
FD_SET(0, &rfds);
FD_SET(fd, &rfds);
// 设置超时时间为5秒钟
tv.tv_sec = 5;
tv.tv_usec = 0;
ret = select(fd+1, &rfds, NULL, NULL, &tv);
if (ret < 0)
{
perror("select error");
exit(-1);
}
else if (0 == ret)
{
printf("Time Out.\n");
}
else
{
// 检测到键盘输入
if (FD_ISSET(0, &rfds))
{
memset(buf, 0, sizeof(buf));
read(0, buf, 8);
printf("The coment of read keyboard is [%s].\n", buf);
}
// 检测到鼠标输入
if (FD_ISSET(fd, &rfds))
{
memset(buf, 0, sizeof(buf));
read(fd, buf, 8);
printf("The coment of read mouse is [%s].\n", buf);
}
}
#endif
// 通过poll函数实现IO多路复用同时读取键盘设备文件和鼠标设备文件
#if 0
fd = open("/dev/input/mouse0", O_RDONLY);
if (fd < 0)
{
perror("open error");
exit(-1);
}
// 初始化pollfd
pfds[0].fd = 0; // 键盘
pfds[0].events = POLLIN; // 等待读操作
pfds[1].fd = fd; // 鼠标
pfds[1].events = POLLIN; // 等待读操作
ret = poll(pfds, fd+1, 5000);
if (ret < 0)
{
perror("poll error");
exit(-1);
}
else if (0 == ret)
{
printf("Time Out.\n");
}
else
{
// 检测到键盘输入或鼠标输入
for (i=0; i<2; i++)
{
if (pfds[i].events == pfds[i].revents)
{
memset(buf, 0, sizeof(buf));
if (0 == i)
{
read(0, buf, 8);
}
if (1 == i)
{
read(fd, buf, 8);
}
printf("The coment of read mouse/keyboard is [%s].\n", buf);
}
}
}
#endif
// 通过异步IO实现同时监测鼠标和键盘的输入
#if 1
mousefd = open("/dev/input/mouse0", O_RDONLY);
if (mousefd < 0)
{
perror("open error");
exit(-1);
}
// 把鼠标的文件描述符设置为可以接受异步IO
flag = fcntl(mousefd, F_GETFL);
flag |= O_ASYNC;
fcntl(mousefd, F_SETFL, flag);
// 把异步IO事件的接收进程设置为当前进程
fcntl(mousefd, F_SETOWN, getpid());
// 注册当前进程的SIGIO信号捕获函数
signal(SIGIO, signal_func);
// 读取键盘设备文件
while (1)
{
memset(buf, 0, sizeof(buf));
ret = read(0, buf, 8);
if (ret > 0)
{
printf("The coment of read keyboard is [%s].\n", buf);
}
}
#endif
return 0;
}
13.keyboard_mouse_thread
/*
* 公司:XXXX
* 作者:Rston
* 博客:http://blog.csdn.net/rston
* GitHub:https://github.com/rston
* 项目:高级IO和多线程和线程同步
* 功能:多进程+多线程。
*/
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#if 1
void *pthread_func(void *arg)
{
char buf[1024];
// 分支任务,读取键盘设备文件
while (1)
{
memset(buf, 0, sizeof(buf));
printf("before read keyboard.\n");
read(0, buf, 8);
printf("The coment of read keyboard is [%s].\n", buf);
}
}
#endif
int main(int argc, char **argv)
{
int fd = -1;
pid_t ret = -1;
char buf[1024];
pthread_t th = -1;
// 通过多进程技术同时读取键盘设备文件和鼠标设备文件
#if 0
ret = fork();
if (ret > 0)
{
// 父进程,读取键盘设备文件
while (1)
{
memset(buf, 0, sizeof(buf));
printf("before read keyboard.\n");
read(0, buf, 8);
printf("The coment of read keyboard is [%s].\n", buf);
}
}
else if (0 == ret)
{
// 子进程,读取鼠标设备文件
fd = open("/dev/input/mouse0", O_RDONLY);
if (fd < 0)
{
perror("open error");
exit(-1);
}
while (1)
{
memset(buf, 0, sizeof(buf));
printf("before read mouse.\n");
read(fd, buf, 8);
printf("The coment of read mouse is [%s].\n", buf);
}
}
else
{
perror("fork error");
exit(-1);
}
#endif
// 通过多线程技术同时读取键盘设备文件和鼠标设备文件
// gcc 13.keyboard_mouse_thread.c -pthread
#if 1
// 创建线程读取键盘设备文件
ret = pthread_create(&th, NULL, pthread_func, NULL);
if (0 != ret)
{
printf("pthread_create error.\n");
return -1;
}
// 因为子线程是while(1)死循环,则其可被主线程分离,分离后主线程不必要再去回收子线程
ret = pthread_detach(th);
if (0 != ret)
{
printf("pthread_detach error.\n");
return -1;
}
// 主任务,读取鼠标设备文件
fd = open("/dev/input/mouse0", O_RDONLY);
if (fd < 0)
{
perror("open error");
return -1;
}
while (1)
{
memset(buf, 0, sizeof(buf));
printf("before read mouse.\n");
read(fd, buf, 8);
printf("The coment of read mouse is [%s].\n", buf);
}
#endif
return 0;
}
13.pthread_sem
/*
* 公司:XXXX
* 作者:Rston
* 博客:http://blog.csdn.net/rston
* GitHub:https://github.com/rston
* 项目:高级IO和多线程和线程同步
* 功能:演示通过信号量解决主线程和子线程之间的同步问题。
*/
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <semaphore.h>
char buf[1024] = {0};
sem_t sem;
unsigned int flag = 0;
void *pthread_func(void *arg)
{
// 分线程阻塞等待信号量
sem_wait(&sem);
while (0 == flag)
{
printf("the cnts of character is %d.\n", strlen(buf));
memset(buf, 0, sizeof(buf));
// 分线程阻塞等待信号量
sem_wait(&sem);
}
// 退出子线程
pthread_exit(NULL);
}
int main(int argc, char **argv)
{
pid_t ret = -1;
int value = -1;
pthread_t th = -1;
// 初始化信号量
value = sem_init(&sem, 0, 0);
if (0 != value)
{
perror("sem_init error");
exit(-1);
}
// 创建子线程处理打印输出计数功能
ret = pthread_create(&th, NULL, pthread_func, NULL);
if (0 != ret)
{
printf("pthread_create error.\n");
return -1;
}
printf("please input the character, input 'end' stop.\n");
while (scanf ("%s", buf))
{
if (!strncmp(buf, "end", strlen("end")))
{
printf("program stop.\n");
flag = 1;
// 信号量加1,激活子线程
sem_post(&sem);
break;
}
// 信号量加1,激活子线程
sem_post(&sem);
}
// 主线程回收子线程
ret = pthread_join(th, NULL);
if (0 != ret)
{
printf("pthread_join error.\n");
exit(-1);
}
printf("pthread_join success.\n");
// 主线程销毁信号量
value = sem_destroy(&sem);
if (0 != value)
{
perror("sem_destroy error");
exit(-1);
}
printf("sem_destroy success.\n");
return 0;
}
13.pthread_mutex
/*
* 公司:XXXX
* 作者:Rston
* 博客:http://blog.csdn.net/rston
* GitHub:https://github.com/rston
* 项目:高级IO和多线程和线程同步
* 功能:演示通过互斥锁解决主线程和子线程之间的同步问题。
*/
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
char buf[1024] = {0};
unsigned int flag = 0;
int value = -1;
pthread_mutex_t mutex;
void *pthread_func(void *arg)
{
// 确保主线程被CPU优先调度运行到上锁处
sleep(1);
while (0 == flag)
{
// 上锁
value = pthread_mutex_lock(&mutex);
if (0 != value)
{
printf("pthread_mutex_lock error.\n");
exit(-1);
}
printf("the cnts of character is %d.\n", strlen(buf));
memset(buf, 0, sizeof(buf));
// 解锁
value = pthread_mutex_unlock(&mutex);
if (0 != value)
{
printf("pthread_mutex_unlock error.\n");
exit(-1);
}
// 确保CPU调度主线程继续运行监控输入
sleep(1);
}
// 退出子线程
pthread_exit(NULL);
}
int main(int argc, char **argv)
{
pid_t ret = -1;
pthread_t th = -1;
// 初始化创建互斥锁
value = pthread_mutex_init(&mutex, NULL);
if (0 != value)
{
printf("pthread_mutex_init error.\n");
exit(-1);
}
// 创建子线程处理打印输出计数功能
ret = pthread_create(&th, NULL, pthread_func, NULL);
if (0 != ret)
{
printf("pthread_create error.\n");
return -1;
}
printf("please input the character, input 'end' stop.\n");
while (1)
{
// 上锁
value = pthread_mutex_lock(&mutex);
if (0 != value)
{
printf("pthread_mutex_lock error.\n");
exit(-1);
}
scanf ("%s", buf);
// 解锁
value = pthread_mutex_unlock(&mutex);
if (0 != value)
{
printf("pthread_mutex_unlock error.\n");
exit(-1);
}
if (!strncmp(buf, "end", strlen("end")))
{
printf("program stop.\n");
flag = 1;
break;
}
// 防止主线程反复上锁,确保主线程经过前面的上锁解锁操作后,子线程被成功调度
sleep(1);
}
// 主线程回收子线程
ret = pthread_join(th, NULL);
if (0 != ret)
{
printf("pthread_join error.\n");
exit(-1);
}
printf("pthread_join success.\n");
// 销毁互斥锁
value = pthread_mutex_destroy(&mutex);
if (0 != value)
{
printf("pthread_mutex_destroy error.\n");
exit(-1);
}
printf("pthread_mutex_destroy success.\n");
return 0;
}
13.pthread_cond
/*
* 公司:XXXX
* 作者:Rston
* 博客:http://blog.csdn.net/rston
* GitHub:https://github.com/rston
* 项目:高级IO和多线程和线程同步
* 功能:演示通过条件变量解决主线程和子线程之间的同步问题。
*/
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
char buf[1024] = {0};
unsigned int flag = 0;
int value = -1;
pthread_mutex_t mutex;
pthread_cond_t cond;
void *pthread_func(void *arg)
{
while (0 == flag)
{
// 上锁
value = pthread_mutex_lock(&mutex);
if (0 != value)
{
printf("pthread_mutex_lock error.\n");
exit(-1);
}
// 阻塞等待主线程发送条件变量
value = pthread_cond_wait(&cond, &mutex);
if (0 != value)
{
printf("pthread_cond_wait error.\n");
exit(-1);
}
printf("the cnts of character is %d.\n", strlen(buf));
memset(buf, 0, sizeof(buf));
// 解锁
value = pthread_mutex_unlock(&mutex);
if (0 != value)
{
printf("pthread_mutex_unlock error.\n");
exit(-1);
}
}
// 退出子线程
pthread_exit(NULL);
}
int main(int argc, char **argv)
{
pid_t ret = -1;
pthread_t th = -1;
// 初始化创建互斥锁
value = pthread_mutex_init(&mutex, NULL);
if (0 != value)
{
printf("pthread_mutex_init error.\n");
exit(-1);
}
// 初始化条件变量
value = pthread_cond_init(&cond, NULL);
if (0 != value)
{
printf("pthread_cond_init error.\n");
exit(-1);
}
// 创建子线程处理打印输出计数功能
ret = pthread_create(&th, NULL, pthread_func, NULL);
if (0 != ret)
{
printf("pthread_create error.\n");
return -1;
}
printf("please input the character, input 'end' stop.\n");
while (1)
{
scanf ("%s", buf);
// 主线程向子线程发送条件变量
value = pthread_cond_signal(&cond);
if (0 != value)
{
printf("pthread_cond_signal error.\n");
exit(-1);
}
if (!strncmp(buf, "end", strlen("end")))
{
printf("program stop.\n");
flag = 1;
break;
}
}
// 主线程回收子线程
ret = pthread_join(th, NULL);
if (0 != ret)
{
printf("pthread_join error.\n");
exit(-1);
}
printf("pthread_join success.\n");
// 销毁条件变量
value = pthread_cond_destroy(&cond);
if (0 != value)
{
printf("pthread_cond_destroy error.\n");
exit(-1);
}
printf("pthread_cond_destroy success.\n");
return 0;
}