今天开始一个比较常用的模块,就是并发编程,可以极大的提高我们程序的运行效率,这部分内容非常重要。那么,让我们开始吧。
🧑🏻作者简介:一个学嵌入式的年轻人
✨联系方式:2201891280(QQ)
📔源码地址:https://gitee.com/xingleigao/study_qianrushi
⏳全文大约阅读时间: 60min
文章目录
线程基础
QQ的多个功能:
- 接收输入
- 通讯
- 显示界面
为了实现多个功能可以使用线程。
进程特点
- 进程有独立的地址空间(不能互相访问)
- Linux为每个进程创建task_struct
- 每个进程都参与内核调度,互不影响
线程
进程在切换时系统开销大,很多操作系统引入了轻量级进程LWP
- 同一进程中的线程共享相同地址空间
- Linux不区分进程、线程
线程特点
通常线程指的是共享相同地址空间的多个任务
使用多线程的好处:
- 大大提高了任务切换的效率
- 避免了额外的TLB & cache的刷新(进程上下文)
线程共享资源
- 可执行的指令
- 静态数据(全局变量)
- 进程中打开的文件描述符
- 当前工作目录
- 用户ID
- 用户组ID
线程私有资源
- 线程ID (TID)
- PC(程序计数器)和相关寄存器
- 堆栈
- 错误号 (errno)
- 优先级
- 执行状态和属性
Linux线程库
线程库功能
pthread线程库中提供了如下基本操作
- 创建线程
- 回收线程
- 结束线程
同步和互斥机制
- 信号量
- 互斥锁
线程创建 – pthread_create
#include <pthread.h> int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*routine)(void *), void *arg);
- 成功返回0,失败时返回错误码
- thread 线程对象
- attr 线程属性,NULL代表默认属性
- routine 线程执行的函数
- arg 传递给routine的参数 ,参数是void * ,注意传递参数格式,
示例代码:
#include <stdio.h> #include <pthread.h> #include <string.h> void * function1(void *arg){ printf("this is thread function\n"); } int main(){ pthread_t tid; int err; err = pthread_create(&tid, NULL, function1, NULL); sleep(1); if(err != 0){ printf("create thread:%s\n",strerror(err)); } return 0; }
注意:
编译的时候需要加入-lpthread
链接线程库!在线程中exit就会全部结束!
线程回收 – pthread_join
#include <pthread.h> int pthread_join(pthread_t thread, void **retval);//*tid 和线程的返回值
成功返回0,失败时返回错误码
thread 要回收的线程对象
调用线程阻塞直到thread结束
*retval 接收线程thread的返回值
线程结束 – pthread_exit
结束当前线程
retval可被其他线程通过pthread_join获取
线程私有资源被释放
示例代码:#include <stdio.h> #include <pthread.h> #include <string.h> void * function1(void *arg){ printf("this is thread function\n"); sleep(1); pthread_exit("funct exit"); } int main(){ pthread_t tid; int err, i; err = pthread_create(&tid, NULL, function1, NULL); void *retval; pthread_join(tid, &retval); printf("retval = %s",(char *)retval); return 0; }
其他函数
这部分就自己看man手册尝试用用吧
- Pthread_join
- Pthread_cancel
- pthread_testcancel
- pthread_setcancelstate
- pthread_setcanceltype
- Pthead_detach
- pthread_attri_init
取消一个线程
- int pthread_cancel(pthread_t thread);
- void pthread_testcancel(void);
- int pthread_setcancelstate(int state, int *oldstate);
- PTHREAD_CANCEL_ENABLE
- PTHREAD_CANCEL_DISABLE
- int pthread_setcanceltype(int type, int *oldtype);
- PTHREAD_CANCEL_DEFERRED
- PTHREAD_CANCEL_ASYNCHRONOUS
线程间通信
线程共享同一进程的地址空间
优点:线程间通信很容易 通过全局变量交换数据
缺点:多个线程访问共享数据时需要同步或互斥机制
同步
同步(synchronization)指的是多个任务按照约定的先后次序相互配合完成一件事情
1968年,Edsgar Dijkstra基于信号量的概念 提出了一种同步机制
由信号量来决定线程是继续运行还是阻塞等待
信号量
信号量代表某一类资源,其值表示系统中该资源的数量
信号量是一个受保护的变量,只能通过三种操作来访问
- 初始化
- P操作(申请资源)
- V操作(释放资源)
Posix 信号量
posix中定义了两类信号量:
- 无名信号量(基于内存的信号量)
- 有名信号量
信号量初始化 – sem_init
#include <semaphore.h> int sem_init(sem_t *sem, int pshared, unsigned int val);
成功时返回0,失败时EOF
sem 指向要初始化的信号量对象
pshared 0 – 线程间 1 – 进程间
val 信号量初值信号量 – P / V 操作
#include <semaphore.h> int sem_wait(sem_t *sem); P操作 int sem_post(sem_t *sem); V操作
成功时返回0,失败时返回EOF
sem 指向要操作的信号量对象
示例代码:#include <stdio.h> #include <pthread.h> #include <string.h> #include <semaphore.h> #include <stdlib.h> char buf[100]; sem_t sem; void *write(void *arg){ while(1){ fgets(buf, 20, stdin); sem_post(&sem); } } void *read(void *arg){ while(1){ sem_wait(&sem); printf("%s\n", buf); memset(buf, 0, sizeof(buf)); } } int main(){ pthread_t tid1, tid2; int re; sem_init(&sem, 0 , 0); re = pthread_create(&tid1, NULL, write, NULL); if(re != 0){ printf("pthread_create: %s\n",strerror(re)); exit(0); } re = pthread_create(&tid2, NULL, read, NULL); if(re != 0){ printf("pthread_create: %s\n",strerror(re)); exit(0); } while(1){ sleep(1); } }
线程通信——互斥
临界资源
一次只允许一个任务(进程、线程)访问的共享资源
临界区
访问临界区的代码
互斥机制
mutex互斥锁
任务访问临界资源前申请锁,访问完后释放锁
互斥锁初始化 – pthread_mutex_init
#include <pthread.h> int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t * attr);
成功时返回0,失败时返回错误码
mutex 指向要初始化的互斥锁对象
attr 互斥锁属性,NULL表示缺省属性
man 函数出现 No manual entry for pthread_mutex_xxx解决办法
apt-get install manpages-posix-dev
申请锁 – pthread_mutex_lock
#include <pthread.h> int pthread_mutex_lock(pthread_mutex_t *mutex);
成功时返回0,失败时返回错误码
mutex 指向要初始化的互斥锁对象
如果无法获得锁,任务阻塞
释放锁 – pthread_mutex_unlock
#include <pthread.h> int pthread_mutex_unlock(pthread_mutex_t *mutex);
成功时返回0,失败时返回错误码
mutex 指向要初始化的互斥锁对象
执行完临界区要及时释放锁
示例代码:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <semaphore.h>
FILE *fp;
pthread_mutex_t mutex;
void *write1(void* arg){
int a=0;
a = (int)arg;
int len,i;
char *c1 = "Hello world\n";
char *c2;
len = strlen(c1);
int td = pthread_self();
pthread_detach(pthread_self());
c2 = c1;
while(1){
pthread_mutex_lock(&mutex);
for(i=0;i<len;i++){
fputc(*c1,fp);
fflush(fp);
c1++;
usleep(10000);
}
pthread_mutex_unlock(&mutex);
c1 = c2;
sleep(1);
}
}
void *write2(void* arg){
int a=0;
a = (int)arg;
int len,i;
char *c1 = "How are your\n";
char *c2;
c2 = c1;
len = strlen(c1);
int td = pthread_self();
pthread_detach(pthread_self());
while(1){
pthread_mutex_lock(&mutex);
for(i=0;i<len;i++){
fputc(*c1,fp);
fflush(fp);
c1++;
usleep(10000);
}
pthread_mutex_unlock(&mutex);
c1 = c2;
sleep(1);
}
}
int main(){
int re,i=0;
pthread_t tid1,tid2;
fp = fopen("1.txt","w");
if(!fp){
perror("fopen");
return -1;
}
pthread_mutex_init(&mutex,NULL);
re = pthread_create(&tid1, NULL,write1, (void *)i);
pthread_detach(tid1);
if(re!=0){
printf("pthread_create:%s\n",strerror(re));
exit(0);
}
re = pthread_create(&tid2, NULL,write2, (void *)i);
pthread_detach(tid2);
if(re!=0){
printf("pthread_create:%s\n",strerror(re));
exit(0);
}
while(1){
sleep(1);
}
}
写在最后
今天讲完了线程相关的内容,这部分内容比较枯燥,建议跟着我的示例代码敲一敲,然后之前我也写过相关的内容建议参照着一起看【C语言有什么用?②】制作一个多线程词频统计工具所有文件我都放在了gitee哦,需要自取,我尽量一天一更,大家和我一起变强呀!明天开始进入进程间通信专题!最后三连即可提高学习效率!!!
另外我在更新的就是算法笔记的一些例题笔记,这个系列是用于提高我的算法能力,如果有兴趣对算法领域感兴趣找不到合适的入门文章也可以追更,如果我更新的太慢了请大家点赞收藏,一键三连才能更有更新的动力呀0.0