Linux系统编程——线程

1 线程基础

1.1 线程概念

线程实际上是应用层的概念,在Linux内核中,所有的调度实体都被称为任务(task),他们之间的区别是:有些任务自己拥有一套完整的资源,而有些任务彼此之间共享一套资源。

1.2 重要API

[1] 创建一条新线程
	int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);
		参数1:新的线程ID
		参数2:线程属性
		参数3:线程例程
		参数4:线程例程的参数
		返回值:成功0 失败errno		
		备注:线程属性设置为NULL,则会创建一个标准属性的线程

POSIX线程库的所有API对返回值的处理都一样:成功返回0,失败返回错误码errno。

[2] 跟线程属性相关的API
		pthread_attr_getaffinity_np( ) 			获取 CPU 亲和度
		pthread_attr_getdetachstate( ) 			获取分离属性---pthread_join()
		pthread_attr_getguardsize( ) 			获取栈警戒区大小
		pthread_attr_getinheritsched( ) 		获取继承策略
		pthread_attr_getschedparam( ) 			获取调度参数-->优先级
		pthread_attr_getschedpolicy( ) 			获取调度策略-->轮转调度  队列(先进先出)
		pthread_attr_getscope( ) 				获取竞争范围
		pthread_attr_getstack( ) 				获取栈指针和栈大小
		pthread_attr_getstackaddr( ) 			已弃用
		pthread_attr_getstacksize( ) 			获取系统的默认线程栈大小
		
		pthread_attr_setaffinity_np( ) 			设置 CPU 亲和度
		pthread_attr_setdetachstate( ) 			设置分离属性
		pthread_attr_setguardsize( ) 			设置栈警戒区大小
		pthread_attr_setinheritsched( ) 		设置继承策略
		pthread_attr_setschedparam( ) 			设置调度参数
		pthread_attr_setschedpolicy( ) 			设置调度策略
		pthread_attr_setscope( ) 				设置竞争范围
		pthread_attr_setstack( ) 				设置栈的位置和栈大小(慎用)
		pthread_attr_setstackaddr( ) 			已弃用
		pthread_attr_setstacksize( ) 			设置栈大小	

线程属性变量的使用步骤:

  1. 定义线程属性变量,并且使用pthread_attr_init()初始化
  2. 使用pthread_attr_setxxxx()来设置相关的属性
  3. 使用该线程属性变量创建相应的线程
  4. 使用pthread_attr_destroy()来销毁该线程属性变量
设置线程的分离属性
	int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
		参数1:线程属性变量
		参数2:PTHREAD_CREATE_DETACHED --->分离
		       PTHREAD_CREATE_JOINABLE --->接合(默认)

一条线程如果是可接合的,意味着这条线程在退出时不会自动释放自身资源,而会成为僵尸线程,同时意味着该线程的退出值可以被其他线程获取。因此,如果不需要某条线程的退出值的话,最好将线程设置为分离状态,以保证该线程不会成为僵尸线程。

[3] 退出线程
	return--->如果main函数调用,则所有线程被退出
	exit()
	void pthread_exit(void *retval);
		参数:退出值
		备注:单独退出该线程,如果main线程调用,并不会退出其他的线程
[4] 线程的接合
	int pthread_join(pthread_t thread, void **retval);
		参数1:线程ID
		参数2:保存退出值,设置为NULL
	int pthread_tryjoin_np(pthread_t thread, void **retval);
		区别:pthread_join()指定的线程在运行,则等待阻塞
			  pthread_tryjoin_np()指定的线程在运行,直接退出
[5] 给指定的线程发一个取消请求
	int pthread_cancel(pthread_t thread);	

2 线程安全

2.1 POSIX信号量

2.1.1 POSIX有名信号量(named-sem)
2.1.1.1 POSIX有名信号量的使用步骤

使用步骤:

  1. 使用sem_open()来创建或者打开一个有名信号量
  2. 使用sem_wait()和sem_post()来分别进行P、V操作
  3. 使用sem_close()关闭
  4. 使用sem_unlink()来删除,并释放系统资源
2.1.1.2 POSIX有名信号量API
[1] 创建、打开一个POSIX有名信号量
	sem_t *sem_open(const char *name, int oflag); //信号量已经存在 则使用
	sem_t *sem_open(const char *name, int oflag,
			       	mode_t mode, unsigned int value); //信号量不存在 则使用
		参数1:信号量的名字
		参数2:O_CREATE---->如果该名字对应的信号量不存在,则创建
			   O_EXCL------>如果该名字对应的信号量存在,则报错
		参数3:八进制读写权限
		参数4:初始值
		返回值:成功-->信号量的地址  失败-->SEM_FAILED
[2] 对POSIX有名信号进行P、V操作
	int sem_wait(sem_t *sem);
	int sem_post(sem_t *sem);
		参数:信号量指针
		返回值:成功0 失败-1
[3] 关闭POSIX有名信号量
	int sem_close(sem_t *sem);
[4] 删除POSIX有名信号量
	int sem_unlink(const char *name);

sem_name.c

#define SHMSIZE 100	//共享内存的大小
#define SEMNAME "sem"	//有名信号量的名字


int main(int argc,char *argv[])
{
	int shmid = shmget(ftok(".",1),SHMSIZE,IPC_CREAT | 0666);
	if(-1 == shmid)
	{
		perror("shmget");
		exit(-1);
	}
	//映射共享内存
	char *shmaddr = shmat(shmid,NULL,0);
	if((void *)-1 == shmaddr)
	{
		perror("shmat");
		exit(-1);
	}
	
	//创建打开信号量
	sem_t *sem = sem_open("sem",O_CREAT,0777,0);
	if(SEM_FAILED == sem)
	{
		perror("sem_open");
		exit(-1);
	}

#ifdef SND
	//每向共享内存写入数据,信号量的值+1
	while(1)
	{
		fgets(shmaddr,SHMSIZE,stdin);
		sem_post(sem);

		if(strncmp(shmaddr,"quit",4) == 0) break;
	}
#elif defined(RCV)
	while(1)
	{
		sem_wait(sem);	
		puts(shmaddr);
		if(strncmp(shmaddr,"quit",4) == 0) break;
	}
#endif	

	sem_close(sem);
	sem_unlink("sem");
	return 0;
}
2.1.2 POSIX无名信号量(unnamed-sem)
2.1.2.1 POSIX无名信号量的使用步骤

使用步骤:

  1. 在这些线程都能访问的区域定义sem_t变量
  2. 在任何线程使用它之前,初始化sem_init()
  3. 分别使用sem_wait()和sem_post()来进行P、V操作
  4. 使用sem_destroy()销毁
2.1.2.2 POSIX无名信号量API
[1] 信号量的初始化
	int sem_init(sem_t *sem, int pshared, unsigned int value);
		参数1:信号量指针
		参数2:信号量的作用范围
			   0为线程间,非0为进程间
		参数3:初始值
		返回值:成功0 失败-1
[2] 信号量的销毁
	int sem_destroy(sem_t *sem);
		返回值:成功0 失败-1
  • 无名信号量一般用于进程的线程之间。
  • POSIX有名信号量属于系统范围内的资源,进程结束,依旧存在。
  • POSIX无名信号量属于进程范围的资源,进程结束,跟随消失。
  • System-V信号量的PV操作对信号量元素加减大于1的数值,而POSIX信号量的PV操作对信号量元素加减1。
  • System-V和POSIX有名信号量适用于进程间的同步互斥,而POSIX无名信号量适用于线程间的同步互斥。

2.2 互斥锁与读写锁

2.2.1 互斥锁
2.2.1.1 互斥锁概念
  • 多个线程对同一个资源进行访问,会相互干扰,需要将这些进程对共享资源的访问隔离起来,使这些线程具有互斥关系。
  • 特点:多个线程访问共享资源的时候是串行的。
  • 缺点:效率低。
  • 步骤:创建互斥锁、初始化、加锁、操作共享资源、解锁。
2.2.1.2 互斥锁API
[1] 初始化互斥锁
	int pthread_mutex_init(pthread_mutex_t *restrict mutex,
              				const pthread_mutexattr_t *restrict attr);
		参数1:锁变量的地址
		参数2NULL
		返回值:成功0 失败非0
	pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
		等价于:pthread_mutex_init(& mutex,NULL);
[2] 加互斥锁
	int pthread_mutex_lock(pthread_mutex_t *mutex);
	int pthread_mutex_trylock(pthread_mutex_t *mutex);(不带阻塞)
[3] 解互斥锁
	int pthread_mutex_unlock(pthread_mutex_t *mutex);
[4] 销毁互斥锁
	int pthread_mutex_destroy(pthread_mutex_t *mutex);
		返回值:成功0 失败非0

mutex.c

pthread_mutex_t mutex;

void output(const char *string)
{
	const char *p =string;
	while(*p != '\0')
	{
		printf("%c",*p);
		usleep(100);
		p++;
	}
	printf("\n");
}

void *routine(void *arg)
{
	pthread_mutex_lock(&mutex);
	output("hello,zix");
	pthread_mutex_unlock(&mutex);

	pthread_exit(NULL);
}

int main(int argc,char *argv[])
{
	pthread_mutex_init(&mutex,NULL);

	pthread_t tid;
	pthread_create(&tid,NULL,routine,NULL);

	pthread_mutex_lock(&mutex);
	output("hello,my LAY");
	pthread_mutex_unlock(&mutex);

	pthread_join(tid,NULL);
	pthread_mutex_destroy(&mutex);

	return 0;
}
2.2.2 读写锁
2.2.2.1 读写锁概念
  • 互斥锁把试图进入临界区的所有其他线程都阻塞,然而有时我们希望在读某个数据与修改某个数据之间做区分。
  • 特点:读共享、写独占、读写不能同时,写的优先级高。
  • 场景:互斥锁:读写串行。读写锁:读并行,写串行。
2.2.2.2 读写锁API
[1] 初始化读写锁
	int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
		      			const pthread_rwlockattr_t *restrict attr);
[2] 加读、写锁
	int pthread_rwlock_rdlock(pthread_rwlock_t *restrict rwlock);
	int pthread_rwlock_tryrdlock(pthread_rwlock_t *restrict rwlock);(不带阻塞)
	
	int pthread_rwlock_wrlock(pthread_rwlock_t *restrict rwlock);
	int pthread_rwlock_trywrlock(pthread_rwlock_t *restrict rwlock);(不带阻塞)
[3] 解读写锁
	int pthread_rwlock_unlock(pthread_rwlock_t *restrict rwlock);
[4] 销毁读写锁
	int pthread_rwlock_destroy(pthread_rwlock_t *restrict rwlock);
		返回值:成功0 失败非0

rwlock.c

pthread_rwlock_t rwlock;
char buf[100];

void output(const char *buf)
{
	const char *p = buf;
	while(*p != '\0')
	{
		printf("%c",*p);
		usleep(1000);
		p++;
	} 
}

void *routine(void *arg)
{
	int i=10;
	while(i--)
	{
		pthread_rwlock_rdlock(&rwlock);
		output(buf);
		pthread_rwlock_unlock(&rwlock);
		usleep(6000);
	}

	pthread_exit(NULL);
}

int main(int argc,char *argv[])
{
	pthread_rwlock_init(&rwlock,NULL);

	pthread_t tid[2];
	pthread_create(&tid[0],NULL,routine,NULL);
	pthread_create(&tid[1],NULL,routine,NULL);

	int i=100;
	while(i--)
	{
		pthread_rwlock_wrlock(&rwlock);
		strcpy(buf,"123456789\n");
		pthread_rwlock_unlock(&rwlock);
		usleep(2000);

		pthread_rwlock_wrlock(&rwlock);
		strcpy(buf,"abcdefjhi\n");
		pthread_rwlock_unlock(&rwlock);
		usleep(2000);
	}

	pthread_join(tid[0],NULL);
	pthread_join(tid[1],NULL);
	pthread_rwlock_destroy(&rwlock);

	return 0;
}

2.3 条件变量

2.3.1 条件变量概念
  • 互斥锁用于上锁,条件变量用于等待。
  • 条件不满足,阻塞线程。条件满足,通知阻塞的线程开始工作。
  • 条件变量引起阻塞,互斥锁保护临界区。

为什么条件变量需要和互斥锁搭配使用?
1.我们需要等待的满足条件是很多线程都可以访问的,因此对这个线程的保护要用到条件锁。
2.一旦一个线程进行了加锁,那么其他线程就无法进入临界区,也就没有办法去改变所等待的条件,而条件变量可以解锁。

2.3.2 条件变量API
[1] 初始化条件变量
	int pthread_cond_init(pthread_cond_t *restrict cond,
              			const pthread_condattr_t *restrict attr);
	pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
		参数1:条件变量
		参数2:条件变量的属性,一般设置为0
		返回值:成功0 失败-1
[2] 阻塞等待一个条件变量
	int pthread_cond_wait(pthread_cond_t *restrict cond,
						pthread_mutex_t *restrict mutex);
		参数1:条件变量
		参数2:互斥锁
		备注:内部处理:1.线程被阻塞,然后解开互斥锁
					  	2.等待条件,直到有线程向它发起通知
					  	3.重新对互斥锁进行加锁操作
	int pthread_cond_timewait(pthread_cond_t *restrict cond,
						pthread_mutex_t *restrict mutex,
						const struct timespec *restrict abstime);
		参数3:超时时间限制
		返回值:成功0 失败-1
[3] 唤醒一个条件变量等待队列中的线程
	int pthread_cond_signal(pthread_cond_t *cond);
		参数1:条件变量
		返回值:成功0 失败-1
[4] 唤醒所有条件变量等待队列中的线程
	int pthread_cond_broadcast(pthread_cond_t *cond);
[5] 销毁条件变量
	int pthread_cond_destroy(pthread_cond_t *cond);
		返回值:成功0 失败-1

cond.c

#define FIFO_PATH "/home/gec/fifo_file"
int fd_fifo;

pthread_mutex_t mutex;
pthread_cond_t cond;

void *routine(void *arg)
{
	pthread_cond_wait(&cond,&mutex);

	char buf[100];
	read(fd_fifo,buf,sizeof(buf));
	puts(buf);
	
	pthread_exit(NULL);
}

int main(int argc,char *argv[])
{
	pthread_cond_init(&cond,NULL);
	pthread_mutex_init(&mutex,NULL);

	/*********create&&open fifo********/
	if(access(FIFO_PATH,F_OK))
	{
		mkfifo(FIFO_PATH,O_CREAT|0666);
	}

	fd_fifo = open(FIFO_PATH,O_RDWR);
	if(fd_fifo == -1)
	{
		perror("open");
		exit(-1);
	}

	/*********create thread********/
	pthread_t tid[5];
	for(int i=0;i<5;i++)
	{
		pthread_create(&tid[i],NULL,routine,NULL);
	}

	/*******do something**********/
	sleep(2);

	/******write fifo && send cond***/
	char buf[100] = "zzzz\niiii\nxxxx\n";
	write(fd_fifo,buf,strlen(buf));
	puts("write is ok");

	pthread_cond_broadcast(&cond);

	/*******destroy**************/
	for(int i=0;i<5;i++)
	{
		pthread_join(tid[i],NULL);
	}
	pthread_mutex_destroy(&mutex);
	pthread_cond_destroy(&cond);
	close(fd_fifo);

	return 0;
}

2.4 其他锁了解

2.4.1 自旋锁
  • 自旋锁与互斥锁的区别:自旋锁阻塞后不会让出CPU,而是继续等待。
  • 存在问题:1.过多占据CPU的事件。2.产生死锁问题。
  • 自旋锁和互斥锁:
    1 自旋锁和互斥锁都是为了实现保护资源共享的机制。
    2 无论是自旋锁还是互斥锁,在任意时刻,最多只有一个保持者。
    3 获得互斥锁的线程,如果锁已经被占用,则该线程将进入休眠状态;获取自旋锁的线程则不会睡眠,而是一直循环等待锁的释放。
2.4.2 递归锁

在同一线程该锁是可重入的,对于不同线程相当于是普通的互斥锁。

2.4.3 乐观锁和悲观锁

悲观锁适合于多写的场合。
乐观锁适合于多读的场合。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值