线程是进程中的一个独立的执行流,由环境(包括寄存器集和程序计数器)和一系列要执行的置零组成。所有进程至少有一个线程组成,多线程的进程包括多个线程,所有线程共享为进程分配的公共地址空间,所以文本段(Text Segment)和数据段(Datan Segment)都是共享的,如果定义一个函数,在各线程中都可以调用,定义一个全局变量,在各个线程中都可以访问到。
从逻辑上看,多线程就就是一个应用程序中。由多个执行部分同时执行,但操作系统并没有将多个线程看做成多个独立的应用实现进程的调度,管理以及资源分配,一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行。
线程是系统调度的基本单位
进程是承担分配资源的一个实体
各线程共享资源
- 数据段各文本段
- 对全局变量的访问
- 文件描述符
- 每种信号的处理方式(SIG_IGN,SIG_DFL或自定义的信号处理函数)
- 当前的工作目录
- 用户的id和组id
各线程私有资源
1. 线程id
2. 上下文,包括寄存器的值,程序计数器和栈指针
3. 栈空间
4. error变量
5. 信号屏蔽字
6. 调度优先级
#include
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
第一个参数thread是用来存储线程id,参数指向线程id的指针。如果在父子进程中创建多个线程,线程id值有可能相同,如果创建成功,在次函数中返回id,如果设置为NULL,将不会返回线程的标号符id
第二个参数 pthread_attr_t *attr用来设置线程属性,一般设置为NULL。
第三个参数是线程运行的代码其实地址,即在此线程中运行那段代码
第四个参数是运行函数的地址。
如果执行成功,返回0,失败返回-1.
2.线程终止
终止线程的三种情况
#include
void pthread_exit(void *retval);
该参数用来保存线程退出状态
3.线程等待
#include
int pthread_join(pthread_t thread, void **retval);
调用该函数的线程将挂起等待,直到id的线程终止。当含糊是返回时,处于等待的线程资源被回收。成功返回0,失败返回错误码。
第一个参数为等待线程的id
第二个参数用户自定义的指针,指向一个保存等待线程的完整退出状态的静态区。
多个线程同时访问共享数据时会发生冲突。例如两个线程对同一个变量进行+1操作,当但两个线程同时进行+1操作时,最后结果只加了一次而非两次。
互斥以排他的方式共享数据被并发执行。我们引入的互斥锁。
互斥锁
互斥锁是一个二元变量,其状态为开锁与上锁,将某个共享资源与某个特定的互斥锁绑定后,对该共享资源的访问如下
pthread_mutex_t lock;
初始化和销毁互斥锁
#include
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
//第一个参数mutex是指向初始化互斥锁的指针
//第二个参数mutexattr是指向属性对象的指针,该属性对象定义的初始化的互斥锁的属性。如果该指针为NULL,则使用默认的属性
int pthread_mutex_destroy(pthread_mutex_t *mutex);
//销毁互斥锁,成功返回0,失败返回错误编码
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//PTHREAD_MUTEX_INITIALIZER,该宏初始化静态分配的互斥锁。对于静态分配初始化的互斥锁,不需要调用pthread_mutex_init()函数
2.申请互斥锁
如果一个线程要占用一共享资源,其必须先给互斥锁上锁
#include
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);
//释放互斥锁释放操作只能由占有改该互斥锁的的线程来完成
条件变量是线程的一种同步机制,条件变量给多个线程提供了一个会和的场所。
条件变量是用变量的形式来描述临界资源的是否在被访问,如果资源不能被访问,则申请互斥锁的执行流就会挂起到某个条件变量上,同时等待唤醒。
条件变量本身是被互斥量保护的,条件变量不能单独使用,必须配合互斥锁一起实现对资源的互斥访问。
基本操作
1.初始化,销毁条件变量
#include
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
//定义条件变量(全局变量)
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
//初始化条件变量
//第一个参数是定义的条件变量的指针
//第二个参数是指向属性对象的指针,该属性对象定义要初始化的条件变量的特性,一般设置为NULL,设置为默认属性。
int pthread_cond_destroy(pthread_cond_t *cond);
//销毁条件变量
2.通知等待条件变量的线程
#include
int pthread_cond_broadcast(pthread_cond_t *cond);
//用于唤醒等待出现与条件变量关联的条件的所有线程
int pthread_cond_signal(pthread_cond_t *cond);
//用于唤醒等待出现与条件变量关联的条件的第一个线程
3.等待条件变量
#include
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex,
const struct timespec *restrict abstime);
//在指定的时间范围内等待条件变量
//第一个参数是要等条件变量的指针
//第二个参数是指向与条件变量cond关联的互斥锁的指针
//第三个参数是等待过期时的绝对时间,如果在此时间范围内取到该条件变量函数返回。该时间为从1970-1-1:0:0:0以来的秒数,即一个绝对时间。
struct timespec
{
long ts_sec;
long ts_nsec;
};
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
//用来阻塞等待某个条件变量
//第一个参数是要等条件变量的指针
//第二个参数是指向与条件变量cond关联的互斥锁的指针
这两个函数都包含一个互斥锁,如果某线程因等待变量进入等待状态,将隐含释放其申请的互斥锁,同样再返回时,首先要申请到互斥锁对象。
条件变量和互斥锁协同工作
1. 锁定互斥锁
2.测试条件是否满足
3.如果满足,执行操作,完成后释放互斥锁
4.2如果不满足,使用条件变量等待,当另一个线程使此条件满足时,执行步骤3。
生产者与消费者模型,是一个解决多线程同步问题的。生产者和消费者共享一块固定的缓冲区,生产者向这块缓冲区生产数据,消费者从这块缓冲区拿走数据。由于生产者线程和消费者线程共享同一个缓冲区,为了读写数据正确,使用时缓冲区队列时要保证两个线程互斥。生产者线程和消费者线程必须满足:生产者写入缓冲区的数据不能超过缓冲区的容量,消费者读取的数目不能超过生产者写入的数目。
当缓冲区为满时,生产者线程不往缓冲区中写数据
当缓冲区为空时,消费者线程不从缓冲区里读数据
对生产者与消费者模型的“三 二 一”原则
三种关系
生产者与生产者互斥关系
消费者与消费者互斥关系
生产者与消费者同步与互斥关系
二种角色
生产者 消费者
一种交易场所
共享的缓冲区
基于单链表的单生产者与单消费者模型的实现
//定义全局变量的单链表,实现头插头删等操作
//实现线程的同步与互斥定义了全局的互斥锁和条件变量
#include<stdio.h>
#include<pthreaod.h>
#include<stdlib.h>
typedef struct ListNode
{
int data;
struct ListNode* next;
}node_t,*node_p,**node_pp;
node_p head=NULL;
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER;
node_p AllocNode(int x,node_p node)//申请结点
{
node_p temp=(node_p)malloc(sizeof(node_t));
if(temp==NULL)
{
perror("malloc");
exit(1);
}
temp->data=x;
temp->next=node;
return temp;
}
void InitList(node_pp node)//初始化
{
*node=AllocNode(0,NULL);
}
int IsEmpty(node_p node)//判空
{
return node->next==NULL?1:0;
}
void FreeNode(node_p node)//释放结点
{
if(node!=NULL)
{
free(node);
node=NULL;
}
}
void PushFront(int d,node_p node)//头插
{
node_p temp=AllocNode(d,NULL);
temp->next=node->next;
node->next=temp;
}
void PopFront(node_p node,int* out)//头删
{
if(!IsEmpty(node))
{
node_p p=node->next;
node->next=p->next;
*out=p->data;
FreeNode(p);
}
}
void Destory(node_p node)
{
int out=0;
while(!IsEmpty(node))
{
PopFront(node,&out);
}
free(node);
}
void ShowList(node_p node)
{
node_p cur=node->next;
while(!IsEmpty(cur))
{
printf("%d ",cur->data);
cur=cur->next;
}
printf("\n");
}
//作为生产者,生产数据,拿到锁进行操作,往缓存区里放数据,在生产一个数据后,释放锁并唤醒在条件变量等到的第一个线程
void* Productor(void* arg)
{
int data=0;
while(1)
{
pthread_mutex_lock(&lock);
data=rand()%1234;
PushFront(data,head);
printf("productor done..%d\n",data);
pthread_mutex_unlock(&lock);
pthread_cond_signal(&cond);
sleep(1);
}
}
//消费者缓冲区里拿数据,上锁,当缓冲区里没有数据时,就用条件变量阻塞等待。如果有,就消费数据。释放锁
void* Consumer(void* arg)
{
while(1)
{
pthread_mutex_lock(&lock);
int data=0;
while(IsEmpty(head))
{
pthread_cond_wait(&cond,&lock);
printf("consumer wait...\n");
}
PopFront(head,&data);
printf("consume done..%d\n",data);
pthread_mutex_unlock(&lock);
}
}
int main()
{
InitList(&head);
pthread_t consumers,productor;
pthread_create(&consumers,NULL, Consumer,NULL);
pthread_create(&productor,NULL,Productor,NULL);
pthread_join(consumers,NULL);
pthread_join(productor,NULL);
Destory(head);
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&cond);
return 0;
}
#include
int sem_init(sem_t *sem, int pshared, unsigned int value);
//初始化一个semaphor变量,
//第一个参数是定义的semphor变量,
//第二个参数pshared参数为0表示信号量用于进程间同步
//第三个参数表示可用资源的数量
int sem_wait(sem_t *sem);
//可以获得资源(P操作),使信号量的值-1;如果semaphor变量为0时,则挂起等待。(阻塞)
int sem_trywait(sem_t *sem);
//避免阻塞的,不挂起等待,信号量为0时,不会阻塞,返回-1并且将error置为EAGAIT
int sem_post(sem_t *sem);
//使信号量+1,相当于解锁过程,可以释放资源(V操作)。使semaphor变量,
//+1,同时唤醒挂起等待的线程。
int sem_destroy(sem_t *sem);
//当信号量使用完后,丢弃。
基于环形队列的生产者与消费者模型的实现
#include
#include
#include
#include
int buf[64];/利用数组实现环形队列
sem_t sem_blank;//信号量,表示队列中的空格量
sem_t sem_data;//信号量,表示队列中的数据量
void* product(void* arg)
{
int step=0;
int data=0;
while(1)
{
int data=rand()%1234;//产生一个随机数
sem_wait(&sem_blank);//表示的空格的信号量-1,当为0时,挂起等待
buf[step]=data;//放数据
sem_post(&sem_data);//表示数据的信号量+1,唤醒等待的线程。
printf("productor done..%d\n",data);
step++;
step=step%64;
sleep(1);
}
}
//生产者生产数据,当没有空格时,表示空格的信号量为0,sem_wait该线程就挂起等待,sem_post唤醒等待的线程并使对应的信号量+1
void* consume(void* arg)
{
int step=0;
int data=0;
while(1)
{
sem_wait(&sem_data);//表示数据的信号量-1
data=buf[step];//放数据
sem_post(&sem_blank);//表示空格的信号量+1
printf("consumer done..%d\n",data);
step++;
step=step%64;
}
}
//队列中没有空格时,唤醒了消费者线程消费,一样的,取数据当没有数据时,sem_wait使消费者线程挂起等待,sem_post唤醒等待的生产者线程,并将对应的信号量+1
int main()
{
pthread_t p,c;
pthread_create(&p,NULL,product,NULL);
pthread_create(&c,NULL,consume,NULL);
sem_init(&sem_blank,0,64);
sem_init(&sem_data,0,0);
pthread_join(p,NULL);
pthread_join(c,NULL);
sem_destroy(&sem_blank);
sem_destroy(&sem_data);
return 0;
}
实现了环形队列的单生产者与单消费者模型,那么可以在此基础上实现多线程的。
基于环形队列多线程的生产者与消费者模型的实现
在上面实现的基础上实现多线程,需要加互斥锁实现消费者与消费者,生产者与生产者之间的互斥。
#include
#include
#include
#include
int buf[64];
sem_t sem_blank;
sem_t sem_data;
pthread_mutex_t lock1=PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t lock2=PTHREAD_MUTEX_INITIALIZER;
void* product(void* arg)
{
static int step=0;
int data=0;
while(1)
{
pthread_mutex_lock(&lock1);
int data=rand()%1234;
sem_wait(&sem_blank);
buf[step]=data;
sem_post(&sem_data);
pthread_t id=pthread_self();
printf("%dproductor done..%d\n",id,data);
step++;
step=step%64;
pthread_mutex_unlock(&lock1);
sleep(1);
}
}
void* consume(void* arg)
{
static int step=0;
int data=0;
while(1)
{
pthread_mutex_lock(&lock2);
sem_wait(&sem_data);
data=buf[step];
sem_post(&sem_blank);
pthread_t id=pthread_self();
printf("%dconsumer done..%d\n",id,data);
step++;
step=step%64;
pthread_mutex_unlock(&lock2);
sleep(1);
}
}
int main()
{
pthread_t p1,p2,c1,c2;
pthread_create(&p1,NULL,product,NULL);
pthread_create(&c1,NULL,product,NULL);
pthread_create(&p2,NULL,consume,NULL);
pthread_create(&c2,NULL,consume,NULL);
sem_init(&sem_blank,0,64);
sem_init(&sem_data,0,0);
pthread_join(p1,NULL);
pthread_join(p2,NULL);
pthread_join(c1,NULL);
pthread_join(c2,NULL);
sem_destroy(&sem_blank);
sem_destroy(&sem_data);
pthread_mutex_destroy(&lock1);
pthread_mutex_destroy(&lock2);
return 0;
}