苏嵌实训——day15
文章目录
一 线程
线程的概念:线程是进程的进一步抽象。进程包括两个集合。一个资源集合,一个线程的集合,进程是资源分配的最小单位,线程是系统调度的最小单位。每个进程中必然包含一个线程,那么这个线程被称作主线程。线程的本质是一个在运行的线程函数。线程也属于并发,也会拥有自己的资源:如:Pc程序计数器,时间片,堆,栈,线程号等。线程本身并不会去申请资源,而是共享进程的资源。线程也被称为轻量级的进程,线程间共享全局变量,使得通信时会很方便。
线程不属于标准C库函数,也不属于系统调用,而是属于第三方库,libpthread.so
安装库:sudo apt-get install manpages-posix manpages-posix-dev
1.1 线程基本使用
线程的接口函数:
1.pthread_create
头文件:#include <pthread.h>
原型:int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
功能:创建一个线程
参数:
thread:线程号
attr:线程的属性和设置堆栈的大小
分离属性:线程结束后,自己回归空间
非分离属性:线程结束后,由创建者手动去归还空间
一般填写NULL,一般在后面函数中设置
start_routine:函数指针,指向了一个返回值是void*,参数是void*的函数
arg:给线程函数传参使用,一般用于线程间通信使用,如果不传参,填写NULL
返回值:
如果成功返回 0
如果错误返回错误码 strerror();
注意:
1.在线程运行过程中,进程结束时资源释放后,线程没有办法执行了。
2.在多线程中,线程中尽量不调用影响整个进程的函数接口,线程本身安全星星不高,一个线程崩溃会造成进程的崩溃。
3.线程中通信的方式2种:1 全局变量 2.参数arg
注意点:如果一个线程对其进行改变,所有线程访问的值都是改变后的值。
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
//int a = 100;
void *MyFun(void * arg)
{
int *p = (int *)arg;
//int a = *(int *)arg; //线程传参
printf("我是第一个线程\n");
printf("子线程获取A的值为%d\n",*p);
//a = 200;
*p = 200;
}
int main(int argc, char const *argv[])
{
int a = 100;
pthread_t tid = 0;
if(pthread_create(&tid,NULL,MyFun,&a) != 0)
{
printf("线程创建失败!\n");
return -1;
}
printf("线程号为%ld\n",tid);
printf("主线程获取A的值为%d\n",a);
sleep(1);
printf("主线程获取A的值为%d\n",a);
return 0;
}
2.pthread_exit
头文件:#include <pthread.h>
原型:void pthread_exit(void *retval);
功能:结束一个线程
参数:retval:线程结束时的状态,如果不传递填写NULL
返回值:无
3.pthread_join
头文件:#include <pthread.h>
原型:int pthread_join(pthread_t thread, void **retval);
功能:阻塞等待回收指定线程
参数:thread:目标线程号
retval:线程结束时,exit返回的状态
返回值:成功返回0
失败返回错误码
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
//int a = 100;
void *MyFun(void * arg)
{
int *p = (int *)arg;
//int a = *(int *)arg; //线程传参
printf("我是第一个线程\n");
sleep(3);
printf("子线程获取A的值为%d\n",*p);
//a = 200;
*p = 200;
pthread_exit(NULL);
}
int main(int argc, char const *argv[])
{
int a = 100;
pthread_t tid = 0;
if(pthread_create(&tid,NULL,MyFun,&a) != 0)
{
printf("线程创建失败!\n");
return -1;
}
printf("线程号为%ld\n",tid);
printf("主线程获取A的值为%d\n",a);
if(0 != pthread_join(tid,NULL))
{
printf("回收资源失败!\n");
return -1;
}
//printf("主线程获取A的值为%d\n",a);
printf("回收资源成功!\n");
return 0;
}
4.pthread_detach
头文件:#include <pthread.h>
原型:int pthread_detach(pthread_t thread);
功能:设置线程分离属性
参数:thread:目标线程号
返回值:成功返回0
失败返回错误码
注意:设置完分离属性后,线程自动归回资源,无需join回收,join会失败
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
//int a = 100;
void *MyFun(void * arg)
{
int *p = (int *)arg;
//int a = *(int *)arg; //线程传参
printf("我是第一个线程\n");
sleep(3);
printf("子线程获取A的值为%d\n",*p);
//a = 200;
*p = 200;
pthread_exit(NULL);
}
int main(int argc, char const *argv[])
{
int a = 100;
pthread_t tid = 0;
if(pthread_create(&tid,NULL,MyFun,&a) != 0)
{
printf("线程创建失败!\n");
return -1;
}
if(0 != pthread_detach(tid))
{
printf("设置分离属性失败\n");
return -1;
}
printf("线程号为%ld\n",tid);
printf("主线程获取A的值为%d\n",a);
if(0 != pthread_join(tid,NULL))
{
printf("回收资源失败!\n");
return -1;
}
//printf("主线程获取A的值为%d\n",a);
printf("回收资源成功!\n");
return 0;
}
5. pthread_self
原型:pthread_t pthread_self(void)
功能:获取自己的线程号
参数:无
返回值:成功会返回一个线程ID号
6.pthread_cancel
原型:int pthread_cancel(pthread_t thread);
功能:申请结束一个线程
参数:目标结束的线程号
返回值:成功返回0,失败返回非0
#include <stdio.h>
#include <pthread.h>
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
//int a = 100;
void *MyFun(void * arg)
{
int *p = (int *)arg;
int n = 3;
//int a = *(int *)arg; //线程传参
sleep(3);
printf("我是第一个线程,我的线程ID是%ld\n",pthread_self());
printf("子线程获取A的值为%d\n",*p);
//a = 200;
*p = 200;
pthread_exit(NULL);
}
int main(int argc, char const *argv[])
{
int a = 100;
pthread_t tid = 0;
if(pthread_create(&tid,NULL,MyFun,&a) != 0)
{
printf("线程创建失败!\n");
return -1;
}
if(0 != pthread_detach(tid))
{
printf("设置分离属性失败\n");
return -1;
}
pthread_cancel(tid); //发送一个申请取消线程的信号
printf("线程号为%ld\n",tid);
printf("主线程获取A的值为%d\n",a);
//printf("主线程获取A的值为%d\n",a);
printf("回收资源成功!\n");
sleep(5);
return 0;
}
练习3
创建两个线程,一个线程对全局变量count循环相加,每一秒加一次,另一个线程循环打印count的值,每一秒打印一次。
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
int count = 0;
int flag = 0;
void * myfun1(void *arg)
{
while(1)
{
if(flag == 0)
{
count++;
//sleep(1);
flag = 1;
}
}
}
int main(int argc, char const *argv[])
{
pthread_t tid = 0;
if(0 != pthread_create(&tid,NULL,myfun1,NULL))
{
perror("pthread_create");
return -1;
}
while(1)
{
if(flag == 1)
{
printf("count = %d\n",count);
flag = 0;
}
}
return 0;
}
1.2 线程同步和互斥
1.2.1同步概念
什么是同步:线程间也存在着竞态的关系,对有些任务是不友好的,所以说线程提供了可以实现让多个线程同步(按一定顺序去执行)。
1.3 无名信号量:又名信号灯
本质是一个全局特殊变量,这个值不允许小于0,
1.3.1 无名信号灯的接口
- sem_init
头文件:#include <semaphore.h>
原型:int sem_init(sem_t *sem, int pshared, unsigned int value);
功能:初始化信号灯
参数:sem:信号灯变量的指针
pshared:如果是0,代表线程间使用
如果是非0,代表亲缘间进程使用
value:信号的初值
返回值:
成功返回0
失败返回 -1.
- sem_wait
头文件:#include <semaphore.h>
原型:int sem_wait(sem_t *sem);
功能:申请资源 ----又名p操作, 也就是信号灯-1操作
参数:sem:目标信号灯
返回值:
成功返回0
失败返回 -1.
3. sem_post
头文件:#include <semaphore.h>
原型:int sem_post(sem_t *sem);
功能:释放资源----又名V操作 也就是信号灯 +1操作
V操作可以多次执行,但是我i们一般把信号量控制在0和1
参数:sem:目标信号灯
返回值:
成功返回0
失败返回 -1.
- sem_destory
头文件:#include <semaphore.h>
原型:int sem_destroy(sem_t *sem);
功能:销毁一个信号灯
参数:sem:目标信号灯
返回值:
成功返回0
失败返回 -1.
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <semaphore.h>
int count = 0;
//定义无名信号灯
sem_t sem1; //给线程1使用,初值应该为0
sem_t sem2; //给线程2使用,初值应该为1
void * myfun1(void *arg)
{
while(1)
{
//申请资源向信号sem1
sem_wait(&sem1);
count++;
sem_post(&sem2);
}
}
int main(int argc, char const *argv[])
{
//初始化无名信号灯,分别初始化值为 1 0
sem_init(&sem1,0,1);
sem_init(&sem2,0,0);
//开始创建线程
pthread_t tid = 0;
if(0 != pthread_create(&tid,NULL,myfun1,NULL))
{
perror("pthread_create");
return -1;
}
while(1)
{
sem_wait(&sem2);
printf("count = %d\n",count);
sleep(1);
sem_post(&sem1);
}
return 0;
}
练习
共有三个线程,123线程分别打印A B C,循环打印后,最终输出的结果是BCA BCA BCA
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <semaphore.h>
//定义无名信号灯
sem_t sem1;
sem_t sem2;
sem_t sem3;
void* myfun1(void *arg)
{
while(1)
{
sem_wait(&sem1);
printf("B\n");
sem_post(&sem2);
}
}
void* myfun2(void *arg)
{
while(1)
{
sem_wait(&sem2);
printf("C\n");
sem_post(&sem3);
}
}
int main(int argc, char const *argv[])
{
sem_init(&sem1,0,1);
sem_init(&sem2,0,0);
sem_init(&sem3,0,0);
pthread_t tid = 0;
pthread_t tid1 = 0;
if(0 != pthread_create(&tid,NULL,myfun1,NULL))
{
perror("pthread_create");
}
if(0 != pthread_create(&tid,NULL,myfun2,NULL))
{
perror("pthread_create1");
}
while(1)
{
sleep(1);
sem_wait(&sem3);
printf("A\n");
sem_post(&sem1);
}
return 0;
}
1.4 线程间的互斥
互斥:与同步机制不同的点在于无需顺序执行,必须保证同一时间内只允许有一个线程去访问临界资源。实质上是保证同一临界区只允许同一时间运行一个临界资源。
临界资源:多线程共享的易改变的资源
临界区:修改临界资源的代码
注意:同步一定会互斥,但是互斥不一定同步
互斥机制:互斥锁
1.pthread_mutex_init
头文件:#include <pthread.h>
原型:int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
功能:动态初始化一把锁
参数:mutex:锁变量的地址
attr:互斥所得属性,NULL缺省默认
返回值:
总是返回 0
2. 静态初始化一把锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
3. pthread_mutex_lock
头文件:#include <pthread.h>
原型:int pthread_mutex_lock(pthread_mutex_t *mutex);
功能:尝试枷锁,如果没有抢到锁资源,阻塞等待加锁
参数:mutex:锁变量的地址
返回值:
成功返回 0
失败返回非0
4.pthread_mutex_trylock
头文件:#include <pthread.h>
原型:int pthread_mutex_trylock(pthread_mutex_t *mutex);
功能:尝试加锁,如果没有抢到锁资源,报错返回
参数:mutex:锁变量的地址
返回值:
成功返回 0
失败返回非0
5.pthread_mutex_unlock
头文件:#include <pthread.h>
原型:int pthread_mutex_unlock(pthread_mutex_t *mutex);
功能:解锁操作
参数:mutex:锁变量的地址
返回值:
成功返回 0
失败返回非0
6. pthread_mutex_destroy
头文件:#include <pthread.h>
原型:int pthread_mutex_destroy(pthread_mutex_t *mutex);
功能:销毁一把锁
参数:mutex:锁变量的地址
返回值:
成功返回 0
失败返回非0
练习
目标将全局变量a加到100 0000,使用两个线程去做,每个线程应该各加50 0000次,这种情况还需要同步吗?
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
int count = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void * myfun(void * arg)
{
int i = 500000;
while(i)
{
if(-1 == pthread_mutex_lock(&mutex))
{
perror("lock");
return NULL;
}
//临界区 ---》开始
int a = count;
a++;
count = a;
//临界区 ---》结束
if(-1 == pthread_mutex_unlock(&mutex))
{
perror("lock");
return NULL;
}
i--;
}
}
int main(int argc, char const *argv[])
{
pthread_t tid = 0;
if(0 != pthread_create(&tid,NULL,myfun,NULL))
{
perror("create");
return -1;
}
int i = 500000;
while(i)
{
if(-1 == pthread_mutex_lock(&mutex))
{
perror("lock");
return -1;
}
int a = count;
a++;
count = a;
if(-1 == pthread_mutex_unlock(&mutex))
{
perror("lock");
return -1;
}
i--;
}
pthread_join(tid,NULL);
pthread_mutex_destroy(&mutex);
printf("count = %d\n",count);
return 0;
}
练习
卖票,共一百张票,有5个人一起卖,设计5个线程,使其票不能重复卖出,并且打印出是哪个线程卖得第多少张票?
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
int Ticket = 100;
pthread_mutex_t mutex;
void delay()
{
int x = 10000,y;
while(x--)
{
y = 5000;
while(y--);
}
}
void * SaleTicket(void *arg)
{
int cur_ticket;
while (1)
{
pthread_mutex_lock(&mutex);
cur_ticket = Ticket;
if(cur_ticket <=0)
{
pthread_mutex_unlock(&mutex);
break;
}
printf("%ld get %d-th ticket!\n",pthread_self(),cur_ticket);
cur_ticket--;
Ticket = cur_ticket;
pthread_mutex_unlock(&mutex);
delay();
}
}
int main(int argc, char const *argv[])
{
int i,ret;
pthread_t tid[5] = {0};
pthread_mutex_init(&mutex,NULL); //动态初始化一把锁
for(i = 0; i < 5;i++)
{
ret = pthread_create(&tid[i],NULL,SaleTicket,NULL);
if(ret != 0)
{
perror("create");
return -1;
}
}
for(i = 0; i < 5;i++)
{
void *status;
pthread_join(tid[i],&status);
}
pthread_mutex_destroy(&mutex);
return 0;
}
1.5 条件变量
条件变量是为了完成线程间同步指定出来得一种机制,是利用将一个线程挂起等待。然后由另一方发送一个条件成立得信号将其唤醒继续运行得原理。
缺点:等待一方必须要先获取到锁。
1.5.1 函数的接口
1.静态初始化条件变量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
2.动态初始化条件变量
头文件:#include <pthread.h>
原型:int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
功能:动态初始化条件变量
参数:cond:条件变量的地址
attr:使用缺省模式NULL
返回值:
成功返回 0
失败返回非0
3.pthread_cond_wait
头文件:#include <pthread.h>
原型:int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
功能:主动挂起等待信号的到来
参数:cond:条件变量的地址
mutex:锁变量的地址
注意:wait函数和一把锁一起绑定,当这个函数运行后,
1.先进性解锁操作,让这把锁的使用权
2.然后将线程挂起
3.当信号到来时,准备运行之前,需要先尝试加锁
1,2属于原子操作
返回值:
成功返回 0
失败返回非0
4.pthread_cond_signal
头文件:#include <pthread.h>
原型:int pthread_cond_signal(pthread_cond_t *cond);
功能:发送一个条件成立的信号,一次性的,每次只能唤醒一个线程,谁收到谁执行
参数:cond:条件变量的地址
返回值:
成功返回 0
失败返回非0
5. pthread_cond_broadcast
头文件:#include <pthread.h>
原型:int pthread_cond_broadcast(pthread_cond_t *cond);
功能:发送一个条件成立的信号,唤醒所有等待的线程,一个广播信号
参数:cond:条件变量的地址
返回值:
成功返回 0
失败返回非0
6. pthread_cond_destory
头文件:#include <pthread.h>
原型:int pthread_cond_destory(pthread_cond_t *cond);
功能: 销毁一个条件变量
参数:cond:条件变量的地址
返回值:
成功返回 0
失败返回非0
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
int Ticket = 100;
pthread_mutex_t mutex;
pthread_cond_t cond;
void delay()
{
int x = 10000,y;
while(x--)
{
y = 5000;
while(y--);
}
}
void * SaleTicketA(void *arg)
{
int cur_ticket;
while (1)
{
pthread_mutex_lock(&mutex);
cur_ticket = Ticket;
if(cur_ticket <=0)
{
pthread_mutex_unlock(&mutex);
break;
}
if(cur_ticket == 50)
{
pthread_cond_signal(&cond); //唤醒另一个线程
}
printf("A get %d-th ticket!\n",cur_ticket);
cur_ticket--;
Ticket = cur_ticket;
pthread_mutex_unlock(&mutex);
delay();
}
}
void * SaleTicketB(void *arg)
{
int cur_ticket;
while (1)
{
pthread_mutex_lock(&mutex);
cur_ticket = Ticket;
if(cur_ticket <=0)
{
pthread_mutex_unlock(&mutex);
break;
}
if(cur_ticket >= 50)
{
pthread_cond_wait(&cond,&mutex); //释放互斥锁,进入睡眠状态
cur_ticket = Ticket;
}
printf("B get %d-th ticket!\n",cur_ticket);
cur_ticket--;
Ticket = cur_ticket;
pthread_mutex_unlock(&mutex);
delay();
}
}
int main(int argc, char const *argv[])
{
int i,ret;
pthread_t tid[2] = {0};
pthread_mutex_init(&mutex,NULL); //动态初始化一把锁
pthread_cond_init(&cond,NULL); //初始化条件变量
ret = pthread_create(&tid[0],NULL,SaleTicketA,NULL);
if(ret != 0)
{
perror("create");
return -1;
}
ret = pthread_create(&tid[1],NULL,SaleTicketB,NULL);
if(ret != 0)
{
perror("create");
return -1;
}
for(i = 0; i < 2;i++)
{
void *status;
pthread_join(tid[i],&status);
}
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}