嵌入式入门学习笔记,遇到的问题以及心得体会!
DAY28
概述:
一、线程
二、同步和互斥问题
三、如何实现同步
四、如何实现互斥
笔记:
一、线程
1、什么是线程:
(1)线程是轻量级的进程
(2)线程存在于进程内,不能独立存在
(3)线程参与CPU调度,进程是系统资源分配最小单位,线程是系统调度的最小单位
(4)在单核CPU中,多线程并发属于伪并发,但是不牵扯虚拟地址空间的切换,所以开销比进程间切换要小很多
(5)在多核CPU中,多线程可以实现真并发
前面我们已经提到,进程是系统中程序执行和资源分配的基本单位每个进程都拥有自己的数据段,代码段和堆
栈段,这就造成了进程在进行切换时,操作系统的开销比较大。
为了提高效率,操作系统又引入了另一个概念——线程,也称轻量级的进程。线程可以对进程的内存空间和资源进行访问,并与同一进程中的其他线程共享。因此,线程的上下文切换的开销比进程小得多。
一个进程可以拥有多个线程,其中每一个线程共享该进程所拥有的资源。要注意的是,由于线程共享了进程的资源和地址空间,因此,任何线程对于系统资源的操作都会给其他线程带来影响。由此可知,多线程中的同步是非常重要的问题。在多线程系统中,进程与线程的关系图如下:
2、哪些是多线程共享的资源,哪些是线程私有的资源:
线程(Thread):有时候被称为轻量级进程(Lightweight Process, LWP),是程序执行流的最小单元。一个标准的线程由线程ID、当前指令指针(PC)、寄存器集合和堆栈组成。
通常一个进程由一个到多个线程组成,各个线程之间共享程序的内存空间(包括代码段、数据段、堆等)以及一些进成级的资源(如打开文件和信号)。一个经典的线程与进程的关系图如下:
|---------------------------------------------------|
|代码 |数据 |进程空间 |打开文件 |
|---------------------------------------------------|
| |
| |-----------| |-----------| |-----------| |
| |寄存器 | |寄存器 | |寄存器 | |
| |-----------| |-----------| |-----------| |
| |栈 | |栈 | |栈 | |
| |-----------| |-----------| |-----------| |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| |MainThread | |Thread1 | |Thread2 | |
| |-----------| |-----------| |-----------| |
| |
| 进程的地址空间使用分配图 |
|---------------------------------------------------|
3、使用多线程的原因:
<1>某个操作可能会陷入长时间等待,等待的线程会进入睡眠状态,无法继续执行。多线程执行可以有效利用等待的时间。典型的例子是等待网络响应。这可能要花费数秒甚至数十秒。
<2>某个操作(常常是计算)会消耗大量的时间,如果只有一个线程,程序和用户之间的交互会中断。多线程可以让一个线程负责交互,另一个线程负责计算。
<3>程序逻辑本身要求并发操作,例如一个多端下载软件(Bittorrent)
<4>多CPU或多核计算机,本身具备同时执行多个线程的能力,因此单线程程序无法全面地发挥计算机地全部计算能力。
<5>相对于多进程应用程序,多线程在数据共享方面效率要高很多。
4、线程地访问权限:
线程地访问非常*,它可以访问进程内存里的所有数据,甚至包括其他线程的堆栈(如果它知道其他线程的堆栈地址,那么这就是很少见的情况),但实际运用中线程也拥有自己的私有存储空间,包括以下几个方面:
<1>栈:尽管并非完全无法被其他线程访问,但一般情况下仍然可以认为是私有的。
<2>线程局部存储(Thread Local Storage, TLS):线程局部存储是某些操作系统为线程单独提供的私有空间,但通常只具有很有限的容量。
<3>寄存器(包括PC寄存器):寄存器是执行流的基本数据,因此为线程私有。
从C程序员的角度来分析,数据在线程之间是否私有如下表:
|-----------------------|-----------------------------------------------|
|线程私有 |线程之间共享(进程所有) |
|-----------------------|-----------------------------------------------|
|局部变量 |全局变量 |
|函数的参数 |堆上的数据 |
|TLS数据 |函数里的静态变量 |
| |程序代码,任何线程都有权利读取并执行任何代码 |
| |打开的文件,A线程打开的文件可以由B线程读写 |
|-----------------------|-----------------------------------------------|
5、如何创建线程:
pthread_create()
头文件:
#include <pthread.h>
/*
*函数名:pthread_create
*函数功能:创建一个线程
*函数参数:
* pthread_t *thread:
* const pthread_attr_t *attr:创建线程时的参数的指针
* void *(*start_routine) (void *):线程处理函数的指针
* void *arg:线程处理函数的参数
*函数返回值:int:成功返回0,失败返回非零
*/
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
注意:Compile and link with -pthread. 编译时加-lpthread
6、如何让线程执行特定任务:
通过线程处理函数来实现
7、线程的释放:
(1)pthread_detach
头文件:
#include <pthread.h>
/*
*函数名:pthread_detach
*函数功能:销毁一个线程
*函数的参数:
* pthread_t thread:要销毁的线程
*函数返回值:int:成功反回0,失败错误码
*/
int pthread_detach(pthread_t thread);
(2)pthread_join
头文件:
#include <pthread.h>
/*
*函数名:pthread_join
*函数功能:等待线程的退出
*函数参数:
* pthread_t thread:要等待的线程
* void **retval:线程的退出状态
*函数返回值:int:成功反回0,失败错误码
*/
int pthread_join(pthread_t thread, void **retval);
(3)pthread_exit
(4)pthread_cancel
8.线程代码演示
#include <stdio.h>
#include <pthread.h>
void *t1f(void *arg)
{
while(1)
{
printf("Tom and Jerry!\n");
}
return NULL;
}
void *t2f(void *arg)
{
while(1)
{
printf("Tomats\n");
}
return NULL;
}
int main()
{
//create thread two
pthread_t tid1, tid2;
int ret = pthread_create(&tid1, NULL, t1f ,NULL);
if(ret != 0)
{
puts("create thread 1 error.");
return -1;
}
ret = pthread_create(&tid2, NULL, t2f ,NULL);
if(ret != 0)
{
puts("create thread 1 error.");
return -1;
}
pthread_detach(tid1);
pthread_detach(tid2);
//detach thread
while(1)
{};
return 0;
}
二、同步和互斥问题
1.什么是临界资源
并发编程中,多线程/进程 共享的资源,都叫临界资源
2.什么是临界区
代码中操作临界资源的部分,称为临界区
3.操作临界资源的使用要注意哪些问题
读读不互斥
读写互斥
写写互斥
4.如何安全的操作临界资源
同步
互斥
三、同步
1.什么是同步:
多进程/多线程在访问临界资源时,按照一定的操作顺序来访问。
2.什么时候使用同步
需要按照一定的顺序访问临界资源时使用
3.实现同步的方法
1.信号量
有名信号量:进程使用
无名信号量:线程使用
2.能够共享的变量
(1)进程:
管道
------------有名管道
------------无名管道
共享内存
消息队列
(2) 线程:
全局变量
静态变量
4.如何实现无名信号量
(1)sem_init
/*需要包含的头文件*/
#include <semaphore.h>
/*
*函数名:sem_init
*函数功能:初始化一个无名信号量
*函数参数:
* sem_t *sem:信号量操作对象的指针
* int pshare:默认为0为线程使用
* unsigned int value:信号量的初始值
*函数返回值:int:成功返回0,失败返回-1
*/
int sem_init(sem_t *sem, int pshare, unsigned int value);
(2)sem_wait
(3)sem_post
(4)sem_destroy
/*需要包含的头文件*/
#include <semaphore.h>
/*
*函数名:sem_destroy
*函数功能:销毁一个无名信号量
*函数参数:sem_t *sem:被销毁的无名信号量的操作对象指针
*函数返回值:int :成功0,失败-1
*/
int sem_destroy(sem_t *sem);
5.如何使用无名信号量
多线程需要同步访问临界资源时使用
6.同步代码演示
(1)flags
create_thread-------------------------------------------------------------------------------------------------代码如下
#include <stdio.h>
#include <pthread.h>
int c;
int flag;
void *t1f(void *arg)
{
while(1)
{
if(flag == 0)
{
if(c < 100)
{
c++;
}
else
{
c = 0;
}
flag = 1;
}
}
return NULL;
}
void *t2f(void *arg)
{
while(1)
{
if(flag == 1)
{
printf("c:%d\n",c);
flag = 0;
}
}
return NULL;
}
int main()
{
//create thread two
pthread_t tid1, tid2;
int ret = pthread_create(&tid1, NULL, t1f ,NULL);
if(ret != 0)
{
puts("create thread 1 error.");
return -1;
}
ret = pthread_create(&tid2, NULL, t2f ,NULL);
if(ret != 0)
{
puts("create thread 1 error.");
return -1;
}
pthread_detach(tid1);
pthread_detach(tid2);
//detach thread
while(1)
{};
return 0;
}
(2)unamed_sem
create_thread-------------------------------------------------------------------------------------------------代码如下
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
int c;
sem_t mysem;
void *t1f(void *arg)
{
while(1)
{
int sval = 0;
sem_getvalue(&mysem, &sval);
if(sval == 2)
{
sem_wait(&mysem);
if(c < 100)
{
c++;
}
else
{
c = 0;
}
}
}
return NULL;
}
void *t2f(void *arg)
{
while(1)
{
int sval = 0;
sem_getvalue(&mysem, &sval);
if(sval == 1)
{
sem_wait(&mysem);
printf("c:%d\n",c);
sem_post(&mysem);
sem_post(&mysem);
}
}
return NULL;
}
int main()
{
//sem_init
int ret = sem_init(&mysem,0,2);
if(ret != 0)
{
puts("sem_init error.");
return -1;
}
//create thread two
pthread_t tid1, tid2;
ret = pthread_create(&tid1, NULL, t1f ,NULL);
if(ret != 0)
{
puts("create thread 1 error.");
return -1;
}
ret = pthread_create(&tid2, NULL, t2f ,NULL);
if(ret != 0)
{
puts("create thread 1 error.");
return -1;
}
pthread_detach(tid1);
pthread_detach(tid2);
//detach thread
//sem_destroy
sem_destroy(&mysem);
while(1)
{};
return 0;
}
四、互斥
1.什么是互斥
多线程/多进程访问临界资源时,没有顺序要求,只需要满足写写互斥和读写互斥时,此时说的就是不能同时访问临界资源。
2.什么时候使用互斥
写写互斥,读写互斥
3.如何实现互斥锁
<1>初始化互斥锁
头文件:
#include <pthread.h>
/*
*函数名:pthread_mutex_init
*函数功能:初始化互斥锁
*函数参数:
* pthread_mutex_t *mutex:锁的指针
* pthread_mutexattr_t *attr:锁的参数的指针,通常为NULL
*函数返回值:成功为0,失败返回-1
*/
int pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *attr);
<2>加锁
头文件:#include <pthread.h>
/*
*函数名:pthread_mutex_lock
*函数功能:申请锁资源
*函数参数:pthread_mutex_t *mutex:锁的指针
*函数返回值:成功返回0,失败-1
*/
int pthread_mutex_lock(pthread_mutex_t *mutex);
<3>解锁
头文件:
#include <pthread.h>
/*
*函数名:pthread_mutex_unlock
*函数功能:释放互斥锁
*函数参数:pthread_mutex_t *mutex:锁的指针
*函数返回值:成功返回0,失败-1
*/
int pthread_mutex_unlock(pthread_mutex_t *mutex);
4.互斥代码演示:
create_thread-------------------------------------------------------------------------------------------------代码如下
#include <stdio.h>
#include <string.h>
#include <pthread.h>
char buf[100];
pthread_mutex_t mutex;
void *t1f(void *arg)
{
while(1)
{
pthread_mutex_lock(&mutex);
puts(buf);
memset(buf,0,100);
strcpy(buf,"Jerry is a student.");
pthread_mutex_unlock(&mutex);
}
return NULL;
}
void *t2f(void *arg)
{
while(1)
{
pthread_mutex_lock(&mutex);
puts(buf);
memset(buf,0,100);
strcpy(buf,"Ben is a teacher.");
pthread_mutex_unlock(&mutex);
}
return NULL;
}
int main()
{
pthread_mutex_init(&mutex, NULL);
//create thread two
pthread_t tid1, tid2;
int ret = pthread_create(&tid1, NULL, t1f ,NULL);
if(ret != 0)
{
puts("create thread 1 error.");
return -1;
}
ret = pthread_create(&tid2, NULL, t2f ,NULL);
if(ret != 0)
{
puts("create thread 1 error.");
return -1;
}
pthread_detach(tid1);
pthread_detach(tid2);
//detach thread
pthread_mutex_destroy(&mutex);
while(1)
{};
return 0;
}
5.注意:
互斥锁只能由加锁的线程解锁
当临界资源加锁后没有解锁,其他线程/进程无法访问临界资源时,就会产生死锁。
附加:并发编程中经典问题:
1.哲学家共餐问题
2.生产者消费者问题