一、线程理论基础
1、定义
线程( thread )技术早在60年代就被提出,但真正应用多线程到操作系统中去,是在80年代中期,solaris是这方面的佼佼者.传统的Unix也支持线程的概念,但是在一个进程( process )中只允许有一个线程,这样多线程就意味着多进程.现在,多线程技术已经被许多操作系统所支持,包括Windows/NT、Linux( thread )。
2、优点
(1)(与进程相比)
内核(招标公司)
进程(建筑公司)——资源由内核分配
线程(建筑队)——资源来自进程
(2)多线程程序作为一种多任务、并发的工作方式,自身有如下优点:
二、多线程程序设计
1、头文件
2、步骤图解
3、步骤
(1)创建
#include
int pthread_create(pthread_t * tidp,const pthread_attr_t*attr,void*(*start_rtn)(void),void*arg)
(2)终止线程
如果进程中任何一个线程中调用exit或_exit,那么整个进程都会终止。
线程的正常退出方式有∶
(3)线程等待
#include
int pthread_join(pthread t_tid,void **rval_ptr)
#include
#include
#include
#include
char message[]="hello world";
//调用的函数
void *tid_work(void * arg)
{
printf("pthread function in running!Argument is %s\n",(char *)arg);
sleep(3);
//复制函数,将Bye复制到message函数内容中去
strcpy(message,"Bye");
pthread_exit("Thank you for your CPU time!\n");
}
int main()
{
pthread_t tid;
int ret;
void *pthread_val;
ret=pthread_create(&tid,NULL,tid_work,(void *) message);//取线程id的地址,线程属性为空,线程要执行的函数为tid_work,函数的参数为message
if(ret != 0)//返回值部位0时,创建线程失败,返回exit(-1)
{
printf("create pthread error!\n");
exit(-1);
}
//等待第一个线程结束
printf("Waiting for pthread to finish...\n");
//返回值为线程等待的返回值
ret = pthread_join(tid,&pthread_val);
//当返回值不为0时,等待失败,返回exit(-1)
if(ret != 0)
{
perror("pthread join error!");
exit(-1);
}
printf("pthread joined,it returned %s",(char *)pthread_val);
printf("message is now:%s\n",message);
return 0;
}
运行结果:
三、线程同步
1、方法
进行多线程编程,因为无法知道哪个线程会在哪个时候对共享资源进行操作,因此让如何保护共享资源变得复杂,通过下面这些技术的使用,可以解决线程之间对资源的竞争:
2、互斥量Mutex
步骤:
对于动态分配的互斥量,在申请内存(malloc)之后,通过pthread_mutex_init进行初始化
在释放内存(free)前需要调用pthread_mutex_destroy
int pthread_mutex_lock(pthread_mutex_t *mutex)
int pthread_mutex_trylock(pthread_mutex_t *mutex)
返回值:成功则返回0,出错则返回错误编号。
trylock是非阻塞调用模式,如果互斥量没被锁住,trylock函数将对互斥量加锁,并获得对共享资源的访问权限;
如果互斥量被锁住了,trylock函数将不会阻塞等待而直接返回EBUSY,表示共享资源处于忙状态
int pthread_mutex_unlock(pthread_mutex_t *mutex)
3、条件变量
(1)条件变量本身不是锁!但它也可以造成线程阻塞。通常与互斥锁配合使用。给多线程提供一个会合的场所(共享的数据)。
(2)主要应用函数:
(3)函数
(4)生产者消费者条件变量模型
线程同步典型的案例即为生产者消费者模型,而借助条件变量来实现这一模型,是比较常见的一种方法。假定有两个线程,一个模拟生产者行为,一个模拟消费者行为。两个线程同时操作一个共享资源(一般称之为汇聚),生产向其中添加产品,消费者从中消费掉产品。
看如下示例,使用条件变量模拟生产者、消费者问题:
#include
#include
#include
#define BUFFER_SIZE 16
#define OVER -1 //不放东西了
struct prod
{
int buffer[BUFFER_SIZE];
pthread_mutex_t lock;
pthread_cond_t notempty;
pthread_cond_t notfull;
int readpos,writepos;
};
struct prod buf;//结构体类型的变量,对应结构体内的成员,可以减少程序定义次数
void init(struct prod *b)
{
pthread_mutex_init(&b->lock,NULL);
pthread_cond_init(&b->notempty,NULL);
pthread_cond_init(&b->notfull,NULL);
b->readpos = b->writepos = 0;
}
void put(struct prod *b,int data)
{
pthread_mutex_lock(&b->lock);
if ((b->writepos + 1)% BUFFER_SIZE == b->readpos)
{
pthread_cond_wait(&b->notfull,&b->lock);
}
b->buffer[b->writepos]=data;
b->writepos++;
if (b->writepos >= BUFFER_SIZE)
{
b->writepos=0;
}
pthread_cond_signal(&b->notempty);
pthread_mutex_unlock(&b->lock);
}
void * producer(void)
{
int n;
for(n=0;n<20;n++)
{
printf("%2d-----> |\n",n+1);
put(&buf,n+1);
}
put(&buf,OVER);
return NULL;
}
int get(struct prod *b)
{
int data;
pthread_mutex_lock(&b->lock);
if (b->readpos==b->writepos)
{
pthread_cond_wait(&b->notempty,&b->lock);
}
data=b->buffer[b->readpos];
b->readpos =(b->readpos+1) % BUFFER_SIZE;
pthread_cond_signal(&b->notfull);
pthread_mutex_unlock(&b->lock);
}
void * customer(void)
{
int d;
while(1)
{
d=get(&buf);
if(OVER==d)
{
break;
}
printf("|----->%2d\n");
}
return NULL;
}
int main()
{
pthread_t th_p,th_c;
init(&buf);
pthread_create(&th_p,NULL,(void *)producer,NULL);
pthread_create(&th_c,NULL,(void *)customer,NULL);
pthread_join(th_p,NULL);
pthread_join(th_c,NULL);
return 0;
}
4、信号灯
(1)定义
信号量用在多线程多任务同步的,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动作。信号量不一定是锁定某一个资源,而是流程上的概念,比如:有 A,B 两个线程,B 线程要等 A 线程完成某一任务以后再进行自己下面的步骤,这个任务并不一定是锁定某一资源,还可以是进行一些计算或者数据处理之类。
信号量(信号灯)与互斥锁和条件变量的主要不同在于” 灯” 的概念,灯亮则意味着资源可用,灯灭则意味着不可用。信号量主要阻塞线程,不能完全保证线程安全,如果要保证线程安全,需要信号量和互斥锁一起使用。
信号量和条件变量一样用于处理生产者和消费者模型,用于阻塞生产者线程或者消费者线程的运行。信号的类型为 sem_t ,对应的头文件为
#include
(2)主要函数
1)sem_init函数
#include
2)sem_wait函数
// 参数 sem 就是 sem_init() 的第一个参数 // 函数被调用sem中的资源就会被消耗1个, 资源数-1 int sem_wait(sem_t *sem);
3)sem_trywait函数
// 参数 sem 就是 sem_init() 的第一个参数 // 函数被调用sem中的资源就会被消耗1个, 资源数-1 int sem_trywait(sem_t *sem);
4)struct timespec
// 表示的时间是从1971.1.1到某个时间点的时间, 总长度使用秒/纳秒表示 struct timespec { time_t tv_sec; /* Seconds */ long tv_nsec; /* Nanoseconds [0 .. 999999999] */ }; // 调用该函数线程获取sem中的一个资源,当资源数为0时,线程阻塞,在阻塞abs_timeout对应的时长之后,解除阻塞。 // abs_timeout: 阻塞的时间长度, 单位是s, 是从1970.1.1开始计算的 int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
5)sem_post函数
// 调用该函数给sem中的资源数+1 int sem_post(sem_t *sem);
6)sem_getvalue函数
// 查看信号量 sem 中的整形数的当前值, 这个值会被写入到sval指针对应的内存中 // sval是一个传出参数 int sem_getvalue(sem_t *sem, int *sval);
通过这个函数可以查看 sem 中现在拥有的资源个数,通过第二个参数 sval 将数据传出,也就是说第二个参数的作用和返回值是一样的。
(3)生产者与消费者
场景描述:使用信号量实现生产者和消费者模型,生产者有 5 个,往链表头部添加节点,消费者也有 5 个,删除链表头部的节点。
1)总资源数为1
如果生产者和消费者线程使用的信号量对应的总资源数为 1,那么不管线程有多少个,可以工作的线程只有一个,其余线程由于拿不到资源,都被迫阻塞了。
#include
#include
#include
#include
#include
#include
// 链表的节点
struct Node
{
int number;
struct Node* next;
};
// 生产者线程信号量
sem_t psem;
// 消费者线程信号量
sem_t csem;
// 互斥锁变量
pthread_mutex_t mutex;
// 指向头结点的指针
struct Node * head = NULL;
// 生产者的回调函数
void* producer(void* arg)
{
// 一直生产
while(1)
{
// 生产者拿一个信号灯
sem_wait(&psem);
// 创建一个链表的新节点
struct Node* pnew = (struct Node*)malloc(sizeof(struct Node));
// 节点初始化
pnew->number = rand() % 1000;
// 节点的连接, 添加到链表的头部, 新节点就新的头结点
pnew->next = head;
// head指针前移
head = pnew;
printf("+++producer, number = %d, tid = %ld\n", pnew->number, pthread_self());
// 通知消费者消费, 给消费者加信号灯
sem_post(&csem);
// 生产慢一点
sleep(rand() % 3);
}
return NULL;
}
// 消费者的回调函数
void* consumer(void* arg)
{
while(1)
{
sem_wait(&csem);
// 取出链表的头结点, 将其删除
struct Node* pnode = head;
printf("--consumer: number: %d, tid = %ld\n", pnode->number, pthread_self());
head = pnode->next;
free(pnode);
// 通知生产者生成, 给生产者加信号灯
sem_post(&psem);
sleep(rand() % 3);
}
return NULL;
}
int main()
{
// 初始化信号量
// 生产者和消费者拥有的信号灯的总和为1
sem_init(&psem, 0, 1); // 生成者线程一共有1个信号灯
sem_init(&csem, 0, 0); // 消费者线程一共有0个信号灯
// 创建5个生产者, 5个消费者
pthread_t ptid[5];
pthread_t ctid[5];
for(int i=0; i<5; ++i)
{
pthread_create(&ptid[i], NULL, producer, NULL);
}
for(int i=0; i<5; ++i)
{
pthread_create(&ctid[i], NULL, consumer, NULL);
}
// 释放资源
for(int i=0; i<5; ++i)
{
pthread_join(ptid[i], NULL);
}
for(int i=0; i<5; ++i)
{
pthread_join(ctid[i], NULL);
}
sem_destroy(&psem);
sem_destroy(&csem);
return 0;
}
结论:如果生产者和消费者使用的信号量总资源数为 1,那么不会出现生产者线程和消费者线程同时访问共享资源的情况,不管生产者和消费者线程有多少个,它们都是顺序执行的。
2)总资源数大于1
如果生产者和消费者线程使用的信号量对应的总资源数为大于 1,这种场景下出现的情况就比较多了:
以上不管哪一种情况都可能会出现多个线程访问共享资源的情况,如果想防止共享资源出现数据混乱,那么就需要使用互斥锁进行线程同步,处理代码如下:
#include
#include
#include
#include
#include
#include
// 链表的节点
struct Node
{
int number;
struct Node* next;
};
// 生产者线程信号量
sem_t psem;
// 消费者线程信号量
sem_t csem;
// 互斥锁变量
pthread_mutex_t mutex;
// 指向头结点的指针
struct Node * head = NULL;
// 生产者的回调函数
void* producer(void* arg)
{
// 一直生产
while(1)
{
// 生产者拿一个信号灯
sem_wait(&psem);
// 加锁, 这句代码放到 sem_wait()上边, 有可能会造成死锁
pthread_mutex_lock(&mutex);
// 创建一个链表的新节点
struct Node* pnew = (struct Node*)malloc(sizeof(struct Node));
// 节点初始化
pnew->number = rand() % 1000;
// 节点的连接, 添加到链表的头部, 新节点就新的头结点
pnew->next = head;
// head指针前移
head = pnew;
printf("+++producer, number = %d, tid = %ld\n", pnew->number, pthread_self());
pthread_mutex_unlock(&mutex);
// 通知消费者消费
sem_post(&csem);
// 生产慢一点
sleep(rand() % 3);
}
return NULL;
}
// 消费者的回调函数
void* consumer(void* arg)
{
while(1)
{
sem_wait(&csem);
pthread_mutex_lock(&mutex);
struct Node* pnode = head;
printf("--consumer: number: %d, tid = %ld\n", pnode->number, pthread_self());
head = pnode->next;
// 取出链表的头结点, 将其删除
free(pnode);
pthread_mutex_unlock(&mutex);
// 通知生产者生成, 给生产者加信号灯
sem_post(&psem);
sleep(rand() % 3);
}
return NULL;
}
int main()
{
// 初始化信号量
sem_init(&psem, 0, 5); // 生成者线程一共有5个信号灯
sem_init(&csem, 0, 0); // 消费者线程一共有0个信号灯
// 初始化互斥锁
pthread_mutex_init(&mutex, NULL);
// 创建5个生产者, 5个消费者
pthread_t ptid[5];
pthread_t ctid[5];
for(int i=0; i<5; ++i)
{
pthread_create(&ptid[i], NULL, producer, NULL);
}
for(int i=0; i<5; ++i)
{
pthread_create(&ctid[i], NULL, consumer, NULL);
}
// 释放资源
for(int i=0; i<5; ++i)
{
pthread_join(ptid[i], NULL);
}
for(int i=0; i<5; ++i)
{
pthread_join(ctid[i], NULL);
}
sem_destroy(&psem);
sem_destroy(&csem);
pthread_mutex_destroy(&mutex);
return 0;
}