LINUX-并发程序设计线程篇

线程的概念

进程

  • 进程有独立的地址空间
  • 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));
	}
}

1查看线程

2
改进读入超出缓冲区覆盖问题

#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]的值,通知主线程可以继续读取用户输入
    }
}

3

临界资源

线程通信 - 互斥

  • 临界资源
    • 一次只允许一个任务(进程,线程)访问的共享资源
  • 临界区
    • 访问临界区的代码
  • 互斥机制
    • 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选项可以添加宏定义
    4
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值