Linux 多线程
·线程的基本操作
1. 概念
·在一个程序里执行路线,一个程序内部的控制序列。
·一切进程至少有一个线程
2. 线程与进程
·进程是资源竞争的基本单位
·线程是进程执行的最小单位
·线程被包括在进程之中
·如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到。
·两者一般的关系为:单线程进程,单进程多线程,多线程单进程,多进程多线程(一般而言,多进程多线程不处理,因为会造成很多问题)
·进程退出后,它的所有线程也会退出,一般情况下,只有线程退出后,进程才会退出。
3. 线程的优缺点
·创建一个线程的代价比进程小很多
·线程之间的切换需要操作系统的工作要少很多
·线程占用的资源比进程少很多
·程序在等待慢速操作的同时,可执行其他的计算任务。
·性能损失比较严重
·健壮性降低
·缺乏访问控制
·编程的难度会提高
4. 线程的创建
·pthread_create(&tid -》标识符
,NULL, ->默认属性
Rout -》回调函数,想要执行的函数
,NULL -》传给线程启动的参数
int main(void)
{
pthread_t tid;
int ret;
if((ret=pthread_create(&tid,NULL,rout,NULL))!=0)
{
exit(EXIT_FAILURE);
}
int i;
for(; ;)
{
printf("main thread");
sleep(1);
}
5. 线程组
·一个进程对应内核里只有一个进程描述符,对应一个进程。每个线程作为一个独立的调度实体在内核态都有自己的进程描述符,所以进程和内核的描述符变为了1:n的关系,所以引入了线程组
·多线程的进程称为线程组,
显示进程与线程: ps -eLf|head -1 && ps -eLf |grep a.out |grep -v grep
线程ID表示为LWP,多线程之间共用一个进程ID(pid),但是线程ID不同(tid)。
·线程的ID由gettid来返回
·线程组的ID与主线程的线程组id相同,在线程组中,所有的线程都是对等的关系
·主线程和进程的ID相同。
1. 线程ID以及进程地址空间布局
(1)pthread_create(&tid ,NULL, Rout,null)
·在线程创建过程中&tid需要一个函数来获得,这个函数就是
pthread_t pthread_self(void)本质为进程地址空间上的一个地址。
(2)线程终止
void pthread_exit(void* value_ptr)//不指向一个局部变量
int pthread_cancel(pthread_t thread);//线程ID,成功返回0,失败返回错误码
1. 线程等待与分离
·有很多时候,已经退出的线程,其空间没有被释放,仍然在进程的地址空间内;创建新的线程不会复制刚刚已经退出的线程,所以就需要线程等待。
(1)函数
int pthread_join(pthread_t thread,//线程ID
void**value_ptr); //指向一个指针,指向线程的返回值
返回值:成功返回0,失败返回错误码
(2)案例示范
int main(void)
{
pthread_t tid;
void * ret;
pthread_create(&tid,NULL,thread1,NULL);
pthread_join(tid,&ret);//等待线程1结束之后,释放空间
printf("thread 1");
free(ret);
pthread_create(&tid,NULL,thread2,NULL);//然后进行线程2
pthread_join(tid,&ret);
printf("thread 2");
}
·线程的同步与互斥
1. 互斥量
(1)·代码必须要有互斥行为:当代码进入临界执行时,不允许其他线程进入该临界区内。
·如果多个线程需要同时执行临界区代码,并且该临界区没有线程在执行,那么只允许一个线程进入该临界区。
·如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区。
所以,这时就需要一个锁,互斥量
(2)互斥量的初始化
·案例为售票系统
#include"stdio.h"
#include"stdlib.h"
#include"string.h"
#include"unistd.h"
#include"pthread.h"
#include"sched.h"
int ticket=100;
pthread_mutex_t mutex;
void *rout(void *arg)//当进入其中一个线程时,即其中有一个在买票时,要进行上锁,执行临界区的代码,则其他的用户不能进入这个临界区执行代码
{
char *id=(char *)arg;
while(1)
{
pthread_mutex_lock(&mutex);//上锁,执行临界区代码,1-》0返回等待
if(ticket>0)
{
usleep(1000);
printf("sells ticket:%d\n",ticket);
ticket--;
pthread_mutex_unlock(&mutex);//解锁,置1时返回,返回成功
Else //如果票数变为0,则直接跳出程序,否则会循环
{
pthread_mutex_unlock(&mutex);
break;
}
}}
int main(void)
{
pthread_t t1,t2;//同时有两个用户在买票,所以开辟两个线程
pthread_mutex_init(&mutex,NULL);//初始化互斥量
pthread_create(&t1,NULL,route,"thread 1");
pthread_create(&t2,NULL,route,"thread 2");
pthread_join(t1,NULL);//等待线程1终止
pthread_join(t2,NULL);
pthread_mutex_destroy(&mutex);//销毁互斥量
}
##注意:已经销毁的互斥量,要确保后面不会有线程之后再尝试加锁
(1)条件变量
·在一个线程互斥访问某一个变量时,在其他线程改变状态之前,它什么也做不了。
·在一个线程访问队列时,发现队列为空,只能等待,直到其他线程将一个节点添加到队列中,也就是**线程。而这种情况的实现需要的条件变量。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
pthread_cond_t cond;//定义一个条件变量
pthread_mutex_t mutex;//定义一个互斥量
void *r1(void *arg)
{
while(1)
{
pthread_cond_wait(&cond,&mutex);//线程等待,因为其他线程没有改变状态
printf("haha\n");
}
}
void *r2(void *arg)
{
while(1)
{
pthread_cond_signal(&cond);//**被阻塞的线程,将信号发送给等待的线程
sleep(1);
}
}
int main(void)
{
pthread_t t1,t2;
pthread_cond_init(&cond,NULL);
pthread_mutex_init(&mutex,NULL);
pthread_create(&t1,NULL,r1,NULL);
pthread_create(&t2,NULL,r2,NULL);
pthread_join(t1,NULL);
pthread_join(t2,NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
}
(2)经典案例:生产者消费者模型
#define CONSUMERS_COUNT 2
#define PRODUSERS_COUNT 2
struct msg
{
struct msg *next;
int num;
};
struct msg *head=NULL;
pthread_cond_t cond;
pthread_mutex_t mutex;
pthread_t threads[CONSUMERS_COUNT +PRODUSRS_COUNT];
void *consumer(void *p)
{
int num =*(int *)p;
free(p);
struct msg *mp;
while(1)
{
pthread_mutex_lock(&mutex);
while( head==NULL)//没有可以消费的东西,可能是信号被打断了,唤醒信号还没有传递过来
{
pthread_cond_wait(&cond,&mutex);//所以等待,等待其他线程的结束或者消费者
}
printf("d% begin consume\n",num);//正常情况下,现场不阻塞,所以正常消费
mp=head;
head=mp->next;
mp->next=mp;
pthread_mutex_unlock(&mutex);
printf("consume d%\n",mp->num);
free(mp);
printf("d% consume end\n",num);
sleep(rand()%5);
}
}
void *producer(void *p)
{
struct msg *mp;
int num =*(int *)p;
free(p);
while(1)
{
printf("d% begin produce\n",num);//开始生产
mp=(struct msg*)malloc(sizeof(struct msg);
mp->num=rand()%1000+1;
printf("produce d%\n",mp->num);//生产中,生产号
pthread_mutex_lock(&mutex);
mp->next=mp;
printf("d% product end",num);
pthread_cond_signal(&cond);//发出信号唤醒正在等待的线程
pthread_mutex_unlock(&mutex);
sleep(rand()%5);
}
int main(void)
{
srand(time(NULL));
pthread_cond_init(&cond,NULL);
pthread_mutex_init(&mutex,NULL);
int i;
for(i=0;i<CONSUMERS_COUNT;i++)
{
int *p=(int*) malloc(sizeof(int));
*p=i;
pthread_create(&thread[i],NULL,consumer,(void*)p);
}//创建消费者的线程
for(i=0;i<PRODUCERS_COUNT;i++)
{
int *p=(int*) malloc(sizeof(int));
*p=i;
pthread_create(&thread[CONSUMERS_COUNT+i],NULL,producer,(void*)p);
}//创建生产者的线程
for(i=0;i<CONSUMERS_COUNT+PRODUCERS_COUNT;i++)
{
pthread_join(&thread[i],NULL);
}//等待两个线程都结束
pthread_destroy(&mutex);
pthread_destroy(&cond);
}
2.同步
POSIX信号量
·与pv操作作用相同,都用于同步操作,达到无冲突访问,用于线程之间的同步。
int sem_init(sem_t *sem,
int pshared,//0表示线程间共享,非0表示进程间共享
unisigned int value);//信号量初始值
int sem_destroy(sem_t *sem);
int sem_wait(sem_t *sem);//若小于0,阻塞,将信号量值减1,相当于v操作
int sem_post(sem_t *sem);//信号量值加1,相当于p操作
·读写锁
·专门处理多读少写的情况
·如果当前的锁是无锁状态,那么读锁和写锁都可以使用
如果是读锁的状态,那么写锁不可以使用,被阻塞
如果是写锁的状态,那么读锁和写锁都被阻塞
·总结:写独占,读共享,写锁优先级高