线程的概念
进程
- 进程有独立的地址空间
- Linux为每个进程创建task_struct
- 每个进程都参与内核调度,互不影响
线程
- 进程在切换时系统开销大
- 很多操作系统引入了轻量级进程LWP
- 同一进程中的线程共享相同地址空间
- Linux不区分进程,线程(均会创建task_struct)
线程的特点
- 通常线程指的是共享相同地址空间的多个任务
- 使用多线程的好处
- 大大提高了任务切换的效率
- 避免了额外的TLB & cache的刷新
线程共享资源 | 线程私有资源 |
---|---|
可执行的指令 | 线程ID(TID) |
静态数据 | PC(程序计数器)和相关寄存器 |
进程中打开的文件描述符 | 堆栈 |
当前工作目录 | 错误号(errno) |
用户ID | 优先级 |
用户组ID | 执行状态和属性 |
线程操作
LINUX线程库
- pthread线程库
- 创建线程
- 回收线程
- 结束线程
- 同步和互斥机制
- 信号量
- 互斥锁
线程创建
#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的参数
线程回收
#include<pthread.h>
int pthread_join(pthread_t thread,void **retval);
- 成功返回0,失败返回错误码
- 调用线程阻塞直到thread结束
- thread:要回收的线程对象
- *retval:接收线程thread的返回值
线程结束
#include<pthread.h>
void pthread_exit(void *retval);
- 结束当前线程
- retval可被其他线程通过pthread_join获取
- 线程私有资源被释放
测试
#include<stdio.h>
#include<string.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>
char message[32] = "Hello World";
void *thread_func(void *arg);
int main(void){
pthread_t a_thread;
void *result;
if(pthread_create(&a_thread,NULL,thread_func,NULL)!=0){
printf("fail to pthread_create\n");
exit(-1);
}
pthread_join(a_thread,&result);
printf("result is %s\n",(char *)result);
printf("message is %s\n",message);
return 0;
}
void *thread_func(void *arg){
sleep(1);
strcpy(message,"marked by thread");
pthread_exit("thank you for waiting for me");
}
同步和互斥机制
线程间通信
- 线程共享同一进程的地址空间
- 优点:线程间通信很容易(通过全局变量交换数据)
- 缺点:多个线程访问共享数据时需要同步或互斥机制
线程通信 - 同步
- 同步指的时多个任务按照约定的先后次序相互配合完成一件事情
- 由信号量来决定线程是继续运行还是阻塞等待
信号量
- 信号量代表某一类资源,其值表示系统中该资源的数量
- 信号量是一个受保护的变量,只能通过三种操作来访问
- 初始化
- P操作(申请资源)
- V操作(释放资源)
P/V操作
- P(S)操作:
if(信号量的值大于0){
申请资源的任务继续运行;
信号量的值减一;
}
else{
申请资源的任务阻塞;
}
- V(S)操作:
信号量的值加一;
if(有任务在等待资源){
唤醒等待的任务,让其继续运行;
}
Posix信号量
- posix中定义了两类信号量:
- 无名信号量(基于内存的信号量)
- 有名信号量
- pthread库常用的信号量操作函数如下:
int sem_init(sem_t *sem,int pshared,unsigned int value);
int sem_wait(sem_t *sem); //P操作
int sem_post(sem_t *sem);
信号量初始化 - 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<string.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>
#include<semaphore.h>
char buf[32];
sem_t sem;
void *function(void *arg);
int main(){
pthread_t a_thread;
if(sem_init(&sem,0,0)<0){
perror("sem_init");
exit(-1);
}
if(pthread_create(&a_thread,NULL,function,NULL)!=0){
printf("fail to pthread_create\n");
exit(-1);
}
do{
fgets(buf,32,stdin);
sem_post(&sem);
}
while(strncmp(buf,"quit",4)!=0);
return 0;
}
void *function(void *arg){
while(1){
sem_wait(&sem);
printf("you enter %lu characters\n",strlen(buf));
}
}
查看线程
改进读入超出缓冲区覆盖问题
#include<stdio.h>
#include<string.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>
#include<semaphore.h>
#define BUF_SIZE 32 //使用宏定义常量,避免魔法数字
char buf[BUF_SIZE]; //使用全局变量存储用户输入的字符串
sem_t sem[2]; //使用信号量数组控制线程同步
void *function(void *arg); //声明子线程函数
int main(){
pthread_t a_thread; //创建子线程标识符
if(sem_init(&sem[0],0,0)<0){ //初始化信号量sem[0]为0
perror("sem_init"); //使用perror函数输出错误信息
exit(-1); //退出程序
}
if(sem_init(&sem[1],0,0)<0){ //初始化信号量sem[1]为0
perror("sem_init");
exit(-1);
}
if(pthread_create(&a_thread,NULL,function,NULL)!=0){ //创建子线程,并执行function函数
printf("fail to pthread_create\n");
exit(-1);
}
do{
fgets(buf,BUF_SIZE,stdin); //从标准输入读取字符串,存入buf中
sem_post(&sem[0]); //增加信号量sem[0]的值,通知子线程可以处理buf中的数据
sem_wait(&sem[1]); //等待信号量sem[1]的值大于零,表示子线程已经处理完毕
} while(strncmp(buf,"quit",4)!=0); //如果输入的字符串不是"quit",则继续循环
sem_destroy(&sem[0]); //销毁信号量sem[0]
sem_destroy(&sem[1]); //销毁信号量sem[1]
return 0;
}
void *function(void *arg){
while(1){
sem_wait(&sem[0]); //等待信号量sem[0]的值大于零,表示主线程已经读取了用户输入的字符串
printf("you enter %lu characters\n",strlen(buf)); //输出buf中字符串的长度(包括换行符)
sem_post(&sem[1]); //增加信号量sem[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表示缺省属性
申请锁 - 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<stdio.h>
#include<string.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>
#include<semaphore.h>
unsigned int count,value1,value2;
pthread_mutex_t lock;
void *function(void *arg);
int main(void){
pthread_t a_thread;
if(pthread_mutex_init(&lock,NULL)!=0){
printf("fail to prhread_mutex_init\n");
exit(-1);
}
if(pthread_create(&a_thread,NULL,function,NULL)!=0){
printf("fail to pthread_creat");
exit(-1);
}
while(1){
count++;
#ifdef _LOCK_
pthread_mutex_lock(&lock);
#endif
value1 = count;
value2 = count;
#ifdef _LOCK_
pthread_mutex_unlock(&lock);
#endif
}
return 0;
}
void *function(void *arg){
while(1){
#ifdef _LOCK_
pthread_mutex_lock(&lock);
#endif
if(value1 != value2){
printf("value1 = %u,value2 = %u\n",value1,value2);
usleep(100000);
}
#ifdef _LOCK_
pthread_mutex_unlock(&lock);
#endif
}
return NULL;
}
- 不使用互斥锁
gcc -o test test.c -lphread
- 使用互斥锁
gcc -o test test.c -lphread -D_LOCK_
//-D选项可以添加宏定义