APUE读书笔记(九)线程及线程同步


每个线程都包含有表示执行环境所必需的信息,其中包括进程中标识线程的ID、一组寄存器值、栈、调度优先级和策略、信号屏蔽字、errno变量以及线程私有数据。

1 线程标识

进程ID在整个系统中是唯一的,但线程ID不同,线程ID只有在它所属的进程上下文中才有意义。

1.1 线程ID的比较函数pthread_equal

线程ID是用pthread_t数据类型来表示的,实现的时候可以用一个结构体来代表pthread_t数据类型,所以可移植的操作系统实现不能把它作为整数处理。因此必须使用一个函数来对两个线程ID进行比较。

#include<pthread.h>
/*pthread_equal两个参数代表两个线程ID,返回值:若相等,返回非0数值;否则,返回0*/
int pthread_equal(pthread_t tidl,pthread_t tid2);

1.2 线程获得自身的线程ID函数pthread_self

#include<pthread.h>
/*pthread_self函数返回值:调用线程的线程ID*/
pthread_t pthread_self(void);

2 线程的基本操作函数

2.1 线程创建函数pthread_create

#include<pthread.h>
/*pthread_create函数参数:
	——tidp:指向新创建线程标识符的指针
	——attr:用来设置线程属性
	——第三个参数是新创建的线程从start_rtn函数的地址开始运行
	——arg:是运行函数的参数,是一个无类型指针参数
函数返回值:若成功,返回0;否则返回错误编号*/
pthread_t pthread_crate(pthread_t *restrict tidp,
						const pthread_attr_t *restrict attr,
						void *(*start_rtn)(void*),void *restrict arg);

2.2 等待线程结束函数pthread_join

#include<pthread.h>
/*pthread_join函数参数:
	——thread:被等待的线程标识符
	——rval_ptr:为一个用户定义的指针,它可以存储被等待线程的返回值;
	            如果对线程的返回值不感兴趣,可以把rval_ptr设置成NULL;
函数返回值:若成功,返回0;否则,返回错误编号*/
int pthread_join(pthread_t thread,void **rval_ptr);

pthread_join函数调用的线程将一直阻塞,直到指定的线程调用pthread_exit、从启动例程中返回或者被取消。如果线程简单地从它的启动例程返回,rval_ptr就包含返回码。如果线程被取消,有rval_ptr指定的内存单元就设置成PTHREAD_CANCELED。
可以通过调用pthread_join自动把线程置于分离状态,这样资源就可以恢复。如果线程已处于分离状态,pthread_join调用就会失败,返回EINVAL。

2.3 线程终止函数pthread_exit

单个线程可以通过以下3种方式退出,终止进程的另说:

  1. 线程可以简单地从启动例程中返回,返回值是线程的退出码。
  2. 线程可以被同一进程的其他线程取消。
  3. 线程调用pthread_exit。
#include<pthread.h>
/*pthread_join函数参数:
	——rval_ptr:一个无类型指针,调用pthread_join函数后,返回码就是此参数*/
void pthrea_exit(void *rval_ptr);

2.4 实例

#include<stdio.h>
#include<pthread.h>
void *thread_func(void *num){ 
        int *num1;
        num1=(int *)num;
        int i;
        printf("this is thread.\n");
        printf("thread ID:%lu\n",pthread_self()); //打印线程ID
        for(i=0;i<3;i++){
                printf("num=%d\n",num1[i]);
        }
        pthread_exit((void *)num1); //返回码是数组num1
}

int main(int argc,char** argv){
        pthread_t tid;
        int *fp;
        int num[3] = {5,6,7};
        if(pthread_create(&tid,NULL,thread_func,(void*)num) == 0){
                printf("thread create succeed\n");
        }
        sleep(1);//延迟一秒保证子线程先执行
        printf("this is main thread.\n");
        pthread_join(tid,(void *)&fp); //fp即为pthread_wait函数的返回码,
        								//也就是和num一样的数组
        int i;
        for(i=0;i<3;i++){
                printf("num=%d\n",fp[i]);
        }
        return 0;
}

输出结果:
在这里插入图片描述

3 线程同步

3.1 互斥量

互斥量(mutex)从本质上说是一把锁,在访问共享资源前对互斥量进行设置(加锁),在访问完成后释放(解锁)互斥量。对互斥量进行加锁以后,任何其他试图再次对互斥量加锁的线程都会被阻塞直到当前线程释放该互斥锁。如果释放互斥量时有一个以上的线程阻塞,那么所有该锁上的阻塞线程都会变成可运行状态,第一个变为运行的线程就可以对互斥量加锁,其他线程就会看到互斥量依然是锁着的,只能回去再次等待它重新变为可用。在这种方式下,每次只有一个线程可以向前执行。

3.1.1 互斥量的各种函数原型

#include<pthread.h>

pthread_mutex_t mutex; //互斥锁的声明

mutex = PTHREAD_MUTEX_INITIALIZER;//静态初始化,下面的函数是动态初始化

/*
pthread_mutex_init函数参数说明:
	——mutex:即将要初始化的互斥锁变量
	——attr:互斥锁的属性,如果是NULL,则用的是默认的属性
函数返回值:若成功,返回0;否则,返回错误编号
*/
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
						const pthread_mutexattr_t *restrict attr);

/*
如果动态分配互斥量(例如,通过调用malloc函数),在释放内存前需要调用pthread_mutex_destroy。
函数pthread_mutex_destroy返回值:若成功,返回0;否则,返回错误编号。
*/
int pthread_mutex_destroy(pthread_mutex_t *mutex);

/*
pthread_mutex_lock函数对互斥量加锁,如果互斥量已经上锁,调用线程将阻塞直到互斥量被解锁。返回值:若成功,返回0;否则,返回错误编号。
*/
int pthread_mutex_lock(pthread_mutex_t *mutex);

/*
pthread_mutex_unlock函数对互斥量解锁。
返回值:若成功,返回0;否则,返回错误编号。
*/
int pthread_mutex_unlock(ptherad_mutex_t *mutex);

/*
pthread_mutex_trylock函数是pthread_mutex_lock函数的非阻塞版本,但它发现死锁不可避免时,它会返回相应的信息,程序员可以针对死锁做出相应的处理
返回值:若成功,返回0;否则,返回错误编号。
*/
int pthread_mutex_trylock(pthread_mutex_t *mutex);

3.1.2 实例

#include <stdio.h>
#include <pthread.h>

int n = 10;
int sum = 0;
double average = 0;
pthread_mutex_t mutex1;
pthread_mutex_t mutex2;

void *sum_n(){
        pthread_mutex_lock(&mutex1);//加锁,阻碍求平均数线程
        printf("execute sum\n");
        int i;
        for(i = 1; i <= n; i++){
                sum += i;
        }
        pthread_mutex_unlock(&mutex2);//解锁    
        pthread_exit(NULL);
}

void *average_n(){
        pthread_mutex_lock(&mutex2);//加锁,
        printf("execute average\n");
        average = (double)sum / n;
        pthread_mutex_unlock(&mutex1);//解锁
        pthread_exit(NULL);
}

int main(int argc,char** argv)
{
        pthread_t tid[2];

        //初始化两个互斥锁
        pthread_mutex_init(&mutex1,NULL);
        pthread_mutex_init(&mutex2,NULL);

        //对mutex2进行加锁,使求平均数线程调用pthread_mutex_lock函数阻塞,
       	//保证先计算和,因为求平均数首先要对mutex2加锁,这时加锁,求平均数线程会阻塞
        pthread_mutex_lock(&mutex2);

        //创建两个线程
        pthread_create(&tid[0], NULL, sum_n, NULL);
        pthread_create(&tid[1], NULL, average_n, NULL);
		
		//等待线程结束函数
        pthread_join(tid[0], NULL);
        pthread_join(tid[1], NULL);

        printf("sum = %d\n", sum);
        printf("average = %lf\n", average);

        //销毁互斥量
        pthread_mutex_destroy(&mutex1);
        pthread_mutex_destroy(&mutex2);
        return 0;
}

运行结果:
在这里插入图片描述

3.2 读写锁

读写锁可以有3种状态:读模式下加锁状态,写模式下加锁状态,不加锁状态。一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。

3.2.1 读写锁各种函数原型

#include<pthread.h>

pthread_rwlock_t rwlock; //声明读写锁

/*
pthread_rwlock_init函数对读写锁进行初始化,其中参数attr是设置读写锁的属性,
											当其值为NULL时代表默认的属性。
返回值:若成功,返回0;否则,返回错误编号。
*/
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
						const pthread_rwlockattr_t *restrict attr);

/*
pthread_rwlock_destroy函数销毁读写锁。
返回值:若成功,返回0;否则,返回错误编号。
*/
int pthread_rwlock_destroy(phtread_rwlock_t *rwlock);

/*
pthread_rwlock_rdlock函数是在读模式下锁定读写锁。
返回值:若成功,返回0;否则,返回错误编号。
*/;
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

/*
pthread_rwlock_wrlock函数是在写模式下锁定读写锁。
返回值:若成功,返回0;否则,返回错误编号。
*/;
int pthread_rwlock_wrlock(pthread_rwlock_t * rwlock);

/*
不管以何种方式锁住读写锁,都可以调用pthread_rwlock_nulock函数解锁。
返回值:若成功,返回0;否则,返回错误编号。
*/;
int pthread_rwlock_unlock(phtrad_rwlock_t *rwlock);

3.2.2 实例

#include <stdio.h>
#include <pthread.h>

pthread_rwlock_t readlock; //读锁
pthread_rwlock_t writelock; //写锁
int CountReader = 0; //读者数量
int CountWriter = 0; //写者数量

void *read(){
	printf("this is read thread\n");
	
	//再来一个读者时,为读进程加锁,读者数加1,开始读
	pthread_rwlock_rdlock(&readlock);
	CountReader++;
	printf("第%d个读者\n", CountReader);

	if(CountReader > 0){//如果读者大于0,写进程加锁
		pthread_rwlock_wrlock(&writelock);
	}

	//若有读者读完,读者未离开,解锁	
	pthread_rwlock_unlock(&readlock);
	printf("第%d个读者读完\n", CountReader);
	
	//某个读者确实离开,执行读者减1操作
	pthread_rwlock_rdlock(&readlock);
	CountReader--;

	if(CountReader == 0){//如果读者都离开了,为写进程解锁
		pthread_rwlock_unlock(&writelock);
	}
	
	//在读进程中,读线程随时解锁
	pthread_rwlock_unlock(&readlock);
}

void *write(){
	printf("this is write thread\n");
	//当有写者在写文件时,读者不能进行读操作,并且其他读写者也阻塞,不能同时进行写操作
	pthread_rwlock_wrlock(&writelock);
	CountWriter++;
	printf("第%d个写者\n", CountWriter);
	if(CountWriter > 0){
		pthread_rwlock_rdlock(&readlock);
	}
	printf("第%d个写者写完\n", CountWriter);
	CountWriter--;
	if(CountWriter == 0){
		pthread_rwlock_unlock(&readlock);
		pthread_rwlock_unlock(&writelock);
	}
}

int main()
{
	// 创建两个线程实现一个线程读文件,一个线程写文件
	pthread_t tid[2];

	pthread_rwlock_init(&readlock, NULL);
	pthread_rwlock_init(&writelock, NULL);

	pthread_create(&tid[0], NULL, read, NULL);
	pthread_create(&tid[1], NULL, write, NULL);

	pthread_join(tid[0], NULL);
	pthread_join(tid[1], NULL);

	pthread_rwlock_destroy(&readlock);
	pthread_rwlock_destroy(&writelock);

	return 0;
}

运行结果:
在这里插入图片描述

3.3 条件变量

条件变量功能:
(1)条件变量是用来等待而不是用来上锁的。它是通过一种能够挂起当前正在执行的进程或放弃当前进程知道在共享数据上的一些条件得以满足。
(2)条件变量操作过程是:首先通知条件变量,然后等待,同时挂起当前进程直到有另外一个进程通知该条件变量为止。
(3)条件本身是由互斥量保护的。线程在改变条件状态之前必须首先锁住互斥量。其他线程在获得互斥量之前不会察觉到这种改变,因为互斥量必须在锁定以后才能计算条件。

3.3.1 条件变量的各种函数原型

#include<pthread.h>

ptherad_cond_t cond; //条件变量的声明

cond = PTHREAD_COND_INITIALIZER; //静态初始化条件变量,下面的函数是动态初始化cond

/*
pthread_cond_init函数对条件变量进行初始化,其中参数attr是设置条件变量的属性,
											当其值为NULL时代表默认的属性。
返回值:若成功,返回0;否则,返回错误编号。
*/
int pthread_cond_init(pthread_cond_t *restrict cond,
					const pthread_condattr_t *restrict attr);

/*
pthread_cond_destroy函数销毁条件变量。
返回值:若成功,返回0;否则,返回错误编号。
*/
int pthread_cond_destroy(pthread_cond_t *cond);

/*
传递给pthread_cond_wait函数的互斥量mutex对条件进行保护。
调用者把锁住的互斥量传给函数,函数然后自动把调用线程放到等待条件的线程列表上,
对互斥量进行解锁。
这就关闭了条件检查和进程进入休眠状态等待条件改变这两个操作之间的时间通道,这样线程
就不会错过条件的任何变化。
pthread_cond_wait返回时,互斥量再次被锁住。
返回值:若成功,返回0;否则,返回错误编号。
*/
int pthread_cond_wait(pthread_cond_t *restrict cond,
						pthread_mutex *restrict mutex);

/*
以下两个函数可以用于通知线程条件已经满足。
pthread_cond_signal函数至少能唤醒一个等待该条件的线程,
而pthread_cond_broadcast函数则能唤醒等待该条件的所有线程。
必须注意,一定要在改变条件状态以后再给线程发信号。
两个函数返回值:若成功,返回0;否则,返回错误编号。
*/
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);

3.3.2 实例

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>

int count=0;

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t product = PTHREAD_COND_INITIALIZER;

void *producter(void* arg){
	while(1){
		pthread_mutex_lock(&mutex); 
		count++;
		pthread_mutex_unlock(&mutex);
		
		pthread_cond_signal(&product);
		printf("product:%d\n",count);
		sleep(rand()%3);
	}
	
}

void* consumer(void* arg){
	while(1){
		pthread_mutex_lock(&mutex);
		while(count==0)
			pthread_cond_wait(&product,&mutex);
		count--;
		pthread_mutex_unlock(&mutex);
		
		printf("consume:%d\n",count);
		
		sleep(rand()%3);
	}
}

int main(){
	pthread_t ptid,ctid;
	
	pthread_create(&ptid,NULL,producter,NULL);
	pthread_create(&ctid,NULL,consumer,NULL);

	pthread_join(ptid,NULL);
	pthread_join(ctid,NULL);

	return 0;
}

运行结果:
在这里插入图片描述

3.4 自旋锁

自旋锁参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值