一. 线程的基本操作函数
先讲述4个基本线程函数,在调用它们前均要包括pthread.h头文件
1.创建线程函数
int pthread_create(pthread_t *tid,const pthread_attr_t *attr, void *(*func)(void *),void *arg);
注意:
第一个参数为指向线程标识符的指针
第二个参数用来设置线程属性
第三个参数是线程运行函数的起始地址
最后一个参数是运行函数的参数
函数thread 不需要参数,所以最后一个参数设为空指针;第二个参数我们也设为空指针,这样将生成默认属性的线程。创建线程成功后,新创建的线程则运行参数三和参数四确定的函数,原来的线程则继续运行下一行代码。<当创建线程成功时,函数返回0,若不为0则说明创建失败,常见的错误返回代码为EAGAIN和EINVAL>
2.等待线程的结束函数
int pthread_join(pthread_ttid,void**status);
第一个参数为被等待的线程标识符
第二个参数为一个用户定义的指针,它可以用来存储被等待线程的返回值
这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。
3.取自己线程ID函数
pthread_t pthread_self(void);
线程都有一个ID在给定的进程内标识自己。 线程ID由pthread_creat返回,可以取得自己的线程ID。
4.终止线程函数
一个线程的结束有两种途径,一种是函数结束,调用它的线程也就结束,另一种方式是通过函数pthread_exit 来实现。
void pthread_exit(void *status);
注:一个线程不能被多个线程等待,否则第一个接收到信号的线程成功返回,其余调用pthread_join 的线程则返回错误代码ESRCH。
唯一的参数是函数的返回代码
编译程序:gcc –o target source –lpthread
二.简单的多线程编程
举例
#include<stdio.h>
#include<pthread.h>
void thread(void)
{
int i;
for(i=0;i<3;i++)
printf("This is a pthread.\n");
}
运行程序,得到如下结果:
This is the main process。
This is a pthread。
This is the main process。
This is the main process.
This is a pthread.
This is a pthread。
再次运行,可能得到如下结果:
This is a pthread.
This is the main process.
This is a pthread.
This is the main process.
This is a pthread.
This is the main process.
前后两次结果不一样,这是两个线程争夺CPU资源的最终结果。
线程应用中的同步问题
许多函数是不可重入的,即同时不能运行一个函数的多个拷贝
在函数中声明的静态变量常常会带来一些问题,函数的返回值也会有问题
进程中共享的变量必须用关键字volatile来定义
为了保护变量,必须使用信号量、互斥等方法来保证对变量的正确使用
互斥锁
互斥锁用来保证一段时间内只有一个线程在执行一段代码。
先看下面一段代码这是一个读/写程序,它们公用一个缓冲区,并且假定一个缓冲区只能保存一条信息。即缓冲区只有两个状态:有信息或没有信息。
void reader_function(void);
void writer_function(void);
char buffer;
int buffer_has_item=0;
pthread_mutex_t mutex;//这里声明了互斥锁变量mutex,结构pthread_mutex_t为不公开的数据类型,其中包含一个系统分配的属性对象
struct timespec delay;
void main(void)
{
pthread_t reader;
delay.tv_sec=2;/*定义延迟时间*/
delay.tv_nec=0;
pthread_mutex_init(&mutex,NULL);
/*用默认属性初始化一个互斥锁对象*/
pthread_create(&reader,pthread_attr_default,(void *)&reader_function,NULL);
writer_function();
}
void writer_function(void){
while(1) {
pthread_mutex_lock(&mutex);/*锁定互斥锁*/
if(buffer_has_item==O){
buffer=make_new_item();
buffer_has_item=1;
}
pthread_mutex_unlock(&mutex);/*打开互斥锁*/
pthreal_delay_np(&delay);
}
}
void reader_function(void)
{
while(1){
pthread_mutex_lock(&mutex);
If(buffer_has_item==1){
consume_item(buffer);
buffer_has_item=O;
}
pthread_mutex_unlock(&mutex);
pthread_delay_np(&delay);
}
}
锁的声明:pthread_mutex_t mutex;
锁的初始化:pthread_mutex_init(&mutex,NULL);
pthread_mutex_lock 声明开始用互斥锁上锁,此后的代码直至调用pthread_mutex_unlock为止均被上锁,即同一时间只能被一个线程调用执行。
当一个线程执行到pthread_mutex_lock处时,如果此时另一个线程使用该锁,则将阻塞此线程,即程序将等待到另一个线程释放此互斥锁。
pthread_mutex_destroy(phtread_mutex_t * lock);
pthread_delay_np函数,让线程睡眠一段时间,
是为了防止一个线程始终占据此函数。
注意:在使用互斥锁的过程中很有可能会出现死锁:即两个线程试图同时占用两个资源,并按不同的次序锁定相应的互斥锁。此时我们可以使用函数pthread_mutex_trylock,它是函数pthread_mutex_lock的非阻塞版本,当它发现死锁不可避免时,它会返回相应的信息,程序员可以针对死锁做出相应的处理。
读写锁
读写锁是从互斥锁中发展下来的,互斥锁会将试图访问我们定义的保护区的所有进程都阻塞掉,但读写锁与此不同,它会将访问中的读操作和写操作区分开来对待
在某些读数据比改数据频繁的应用中,读写锁将会比互斥锁表现出很大的优越性
读写锁所遵循的规则:
只要没有进程持有某个给定的读写锁用于写,那么任意数目的线程都可持有该读写锁用于读
仅当没有线程持有某个给定的读写锁用于读或写,才能分配该读写锁用于写。
读写锁的声明:pthread_rwlock_t rwlock;
读写锁初始化:pthread_rwlock_init (pthread_rwlock_t *rwlock, pthread_rwlockattr_t *attr);
例: pthread_rwlock_init (&rwlock,NULL);
为读进程获得锁:pthread_rwlock_rdlock(pthread_rwlock_t *lock);
为写进程获得锁:pthread_rwlock_wrlock(pthread_rwlock_t *lock);
读写进程解锁:pthread_rwlock_unlock(pthread_rwlock_t *lock);
销毁读写锁:pthread_rwlock_destroy(pthread_rwlock_t *lock);
上述函数执行成功,返回0,如果不为0,则函数执行失败
条件变量
条件变量是用来等待而不是用来上锁的。它是通过一种能够挂起当前正在执行的进程或放弃当前进程直到在共享数据上的一些条件得以满足。
条件变量操作过程是:首先通知条件变量,然后等待,同时挂起当前进程直到有另外一个进程通知该条件变量为止。
互斥锁一个明显的缺点是它只有两种状态:锁定和非锁定。条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。
工作机制:条件的检测是在互斥锁的保护下进行的。如果一个条件为假,一个线程自动阻塞,并释放等待状态改变的互斥锁。如果另一个线程改变了条件,它发信号给关联的条件变量,唤醒一个或多个等待它的线程,重新获得互斥锁,重新评价条件。如果两进程共享可读写的内存,条件变量可以被用来实现这两进程间的线程同步。
条件变量的声明Pthread_cond_t condtion;
条件变量的初始化pthread_cond_init(pthread_cond_t * cond,pthread_condattr_t *attr);
等待信号pthread_cond_wait(pthread_cond_t * cond,pthread_mutex_t * mutex);
发送信号pthread_cond_signal(pthread_cond_t *cond);
信号量
信号量(semaphore)是另一种加锁操作,与普通加锁不同的是,信号量记录了一个空闲资源数值,信号量的值表示了当前空闲资源的多少。信号量本质上是一个非负的整数计数器,它用来控制对公共资源的访问。
当公共资源增加时,调用函数sem_post()增加信号量
当信号量值大于0时,才能使用公共资源,使用后,函数sem_wait()减少信号量
当它变为0时,进程将主动放弃处理器进入等待对列
sem_init(sem_t *sem, int pshared, unsigned int val)
这个函数初始化一个信号量sem 的值为val,参数pshared 是共享属性控制,表明是否在进程间共享。
sem_wait(sem_t *sem);
调用该函数时,若sem为无状态,调用线程阻塞,等待信号量sem值增加(post )成为有信号状态;若sem为有状态,调用线程顺序执行,但信号量的值减一。
sem_post(sem_t *sem);
调用该函数,信号量sem的值增加,可以从无信号状态变为有信号状态。
sem_destory(sem_t *sem);
释放信号量。
三.实验
事先编辑好数据文件1.dat和2.dat,假设它们的内容分别为1 2 3 4 5 6 7 8 9 10和 -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 , 设计一个程序,在这个程序中一共有3个线程,其中两个线程负责从文件读取数据到公共的缓冲区,另外一个线程从缓冲区读取数据作不同的处理(加和乘运算)。
具体要求:
线程1从1.dat将数据读文件读到buf1中;
线程2从2.dat 将数据读到buf2中;
当buf1,buf2有数据时,线程3将buf1和buf2的结果相加和乘法处理,并将结果显示出来;
实验代码
代码取自https://blog.csdn.net/qq_42316621/article/details/101291603,侵删
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
int buf1,buf2;
sem_t sem1;//是否可以向buf1中读入数据
sem_t sem2;//是否可以向buf2中读入数据
sem_t sem3;//是否可以从buf1中取出数据
sem_t sem4;//是否可以从buf2中取出数据
/* 从1.dat将数据读文件读到buf1中 */
void ReadData1(void)
{
FILE *fp=fopen("1.dat","r");
while(!feof(fp)){
sem_wait(&sem1);
fscanf(fp,"%d",&buf1);
sem_post(&sem3);
}
fclose(fp);
}
/* 从2.dat 将数据读到buf2中 */
void ReadData2(void)
{
FILE *fp=fopen("2.dat","r");
while(!feof(fp)){
sem_wait(&sem2);
fscanf(fp,"%d",&buf2);
sem_post(&sem4);
}
fclose(fp);
}
/* 计算 */
void HandleData(void)
{
while(1){
sem_wait(&sem3);
sem_wait(&sem4);
printf("Pluse: %d+%d=%d\n",buf1,buf2,buf1+buf2);
printf("Multiply: %d*%d=%d\n",buf1,buf2,buf1*buf2);
sem_post(&sem1);
sem_post(&sem2);
}
}
void main()
{
pthread_t t1,t2,t3;
sem_init(&sem1,0,1);
sem_init(&sem2,0,1);
sem_init(&sem3,0,0);
sem_init(&sem4,0,0);
pthread_create(&t1,NULL,(void *)ReadData1,NULL);
pthread_create(&t2,NULL,(void *)ReadData2,NULL);
pthread_create(&t3,NULL,(void *)HandleData,NULL);
pthread_join(t1,NULL);
}