1.线程概念
线程,也叫轻量级进程(实际上pthread是由进程模拟的),线程是程序执行流的最小单元。一个线程由线程ID、PC指针、寄存器集合、和堆栈组成。Linux对线程的实现是通过__clone调用实现的,这个调用会new多个进程出来,只不过的是Linux会设置这些进程的共享内存空间、文件描述符等属性为相同,这样就实现了线程的定义
线程是进程中的一个实体,除乐必不可少的栈区资源以外没有其余系统资源,同一进程下的线程共享系统资源。
当线程数据共享的时候,有可能会出现不安全的情况。后面详谈
每个进程至少有一个线程(单线程是指一个main 函数下只有一条线,多个线程是指一个main 函数下多条并发运行的任务线,类似Ucos),同进程中的所有线程共享进程空间(包括:堆区、代码区、数据区、文件描述符以及信号等),只拥有自己的栈空间。
关于调度:Linux对线程调度,策略和进程是基本一致的。所以,很多人问,为什么一个线程中如果有一个循环的 话,其他线程都得不到执行,这就是因为Linux对线程的调度,策略和进程是一样的,即,如果一个线程处于一个高密度的工作状态的话,或者该线程的cpu slice没用完的话,这个线程是不会被调度的。摘自https://blog.csdn.net/hanchaoman/article/details/6697636
2.多线程
并发:并发是指在同一时刻,只能有一条指令执行,但多个进程指令被快速轮换执行,使得在宏观上具有多个进程同时执行的效果;看起来同时发生,单核。
并行:真正的同时发生
同步:彼此之间有依赖关系的调用不应该同时发生,同步的意义就是阻止事件同时发生(因为cpu是单核处理的,不可能同时运行两条)
异步:两个事件相互独立
3.多进程与多线程
多进程 | 多线程 | |
定义 | 进程是CPU分配资源的最小单位 | 是程序执行流的最下单位 |
空间 | 空间独立,创建会消耗空间 | 共享同一进程下的空间 |
通信 | 用户空间--内核--用户空间 | 同一地址空间 |
稳定性 | 相互独立互不影响 | 一个线程崩溃影响所在进程稳定性 |
单/多 | 单进程单main,多进程多main | 单线程是main中一条执行流,多线程是main中多条执行流 |
运行状态 | 就绪、阻塞、运行 | 就绪、阻塞、运行 |
成员 | 父子进程、始组进程、孤儿僵尸进程 | 主线程(main)、子线程、僵尸线程 |
注意:1.主线程在原本的堆栈上运行,堆栈可以无限增长,而普通线程的堆栈受限制(2M)
4.API
1.获取线程id
pthread_t pthread_self(void)
2.创建线程
函数名称:int pthread_create(pthread*pthread_id,const const pthread_attr_t *attr,,void *(*start_routine) (void *),void *arg)
函数功能:创建一个子线程
函数参数:
pthread*pthread_id:通过传址操作保存子线程id
const const pthread_attr_t *attr:线程创建属性(分离、不分离)
分离:分离一个线程并不影响它,仅仅是当先系统结束时,其所属的资源可以回收,分离的线程终止的时候会保留他的虚拟内存(包括堆栈和其他系统资源),这种线程叫僵尸线程
非分离:NULL
void *(*start_routine) (void *):
void *:返回值类型,所以在创建子线程的时候返回值类型应该是void *
*start_routine:指针类型,本质是地址==函数名
(void *):参数,创建的时候要保持一致
void *arg:传给子函数的数据 遇到void *首先要规定步长
#include<stdio.h>
#include<pthread.h>
#include<string.h>
#include<unistd.h>
void *function(void *arg)
{
char buff[128] = {0};
strcpy(buff,arg);
while(1)
{
printf("son = %lu\n",pthread_self());
printf("arg = %s\n",buff);
sleep(1);
}
}
int main(int argv,char *argc[])
{
char buff[] = "hello i am son";
pthread_t pthread_id;
int flag = pthread_create(&pthread_id,NULL,function,(void *)buff);
if(flag != 0)
{
perror("pthread_create\n");
}
while(1)
{
printf("main = %lu\n",pthread_self());
printf("son = %lu\n",pthread_id);
printf("i am main pthread\n");
sleep(1);
}
return 0;
}
3.线程退出
函数名称:void pthread_exit(void *retval)
函数功能:线程退出
处于主线程:等待所有的子线程都退出了,才结束主线程,即进程结束。
子线程:结束该子线程,并返回一个值
函数形参:void *retval表示线程退出是的状态,不需要则填0
- pthread_exit函数如果放在主线程中则会等待所有子线程退出才能结束主线程。
- 对于线程的结束可以使用return、pthread_exit(exit是结束进程的),如果是多个线程中退出一个,只能用pthread_exit
- 可以使用pthread_join来获取线程退出的时候获得的状态
试想,在主线程创建一个子线程,在该子线程再创建一个子子线程(绝对路径),当子线程关闭的时候子子线程还运行吗?
#include<stdio.h>
#include<pthread.h>
#include<string.h>
#include<unistd.h>
void *function_son(void *arg);
void *function(void *arg)
{
pthread_t pthread_son;
char buff[128] = {0};
strcpy(buff,arg);
int flag = pthread_create(&pthread_son,NULL,function_son,(void *)buff);
for(int i=0;i< 10;i++)
{
printf("子线程:%d\n",i);
sleep(1);
}
pthread_exit(NULL);
}
void *function_son(void *arg)
{
while(1)
{
printf("子子线程\n");
sleep(1);
}
}
int main(int argv,char *argc[])
{
char buff[] = "hello i am son";
pthread_t pthread_id;
int flag = pthread_create(&pthread_id,NULL,function,(void *)buff);
if(flag != 0)
{
perror("pthread_create\n");
}
while(1)
{
//printf("main = %lu\n",pthread_self());
//printf("son = %lu\n",pthread_id);
//printf("i am main pthread\n");
printf("主线程\n");
sleep(1);
}
return 0;
}
子子线程任然在运行,所以子线程退出不影响子子线程
4.线程等待
函数原型:int pthread_join(pthread_t thread, void **retval);
线程ID 接收pthread_exit返回值
函数功能:等待某个指定子线程的退出(等待指定线程的结束,如果不结束,一直等待,等待到指定的子进程结束后,继续往下运行)。
函数形参:
1) pthread_t thread:具体需要等待的线程id。
2) void **retval:接收线程退出时的状态的返回值,与“pthread_exit”函数连用(就是接收“pthread_exit”函数的参数),如果不需要退出状态的时候,直接给NULL。
函数返回值:int:创建成功时返回0,失败时返回一个非0值,并且设置errno变量来指示错误的发生。
注意:线程只有少量的栈区资源,所以线程函数返回的是一个栈区地址。指针函数只能返回静态局部变量,全局变量,或者动态开辟的地址-----不能返回局部变量的地址
#include<stdlib.h>
#include<stdio.h>
#include<pthread.h>
#include<string.h>
#include<unistd.h>
void *function(void *arg)
{
int *p = (int*)malloc(4);
*p = 4;
pthread_exit((void *)p);
}
int main(int argv,char *argc[])
{
char *p = NULL;
char buff[] = "hello i am son";
pthread_t pthread_id;
int flag = pthread_create(&pthread_id,NULL,function,(void *)buff);
if(flag != 0)
{
perror("pthread_create\n");
}
// while(1)
{
printf("主线程\n");
pthread_join(pthread_id,(void *)&p);
printf("p = %d\n",*p);
// sleep(1);
}
return 0;
}
5.线程取消 //由于Linux线程库与c库结合得不好,尽量不要用,linux支持并不完善
函数头文件:#include <pthread.h>
函数原型:int pthread_cancel(pthread_t thread);
函数功能:结束并取消一个正在运行的线程。
函数形参:pthread_t thread:具体需要取消的线程id。
函数返回值:int:创建成功时返回0,失败时返回一个非0值,并且设置errno变量来指示错误的发生。
5.Linux下线程同步机制
前面我们提过Linux下多线程并发是不安全的。主要是由于:
-
原子性:一个或者多个操作在 CPU 执行的过程中被中断
-
可见性:一个线程对共享变量的修改,另外一个线程不能立刻看到
-
有序性:程序执行的顺序没有按照代码的先后顺序执行
先看下列代码段
-
#include<stdio.h> #include<pthread.h> #include<unistd.h> int i = 0; void *function1(void *arg) { for(;i< 1000;i++) { printf("function1:%d\n",i); //sleep(1); } } void *function2(void *arg) { for(;i< 1000;i++) { printf("function2:%d\n",i); //sleep(1); } } void *function3(void *arg) { for(;i< 1000;i++) { printf("function3:%d\n",i); //sleep(1); } } int main(int argv,char *argc[]) { pthread_t pthread_id1,pthread_id2,pthread_id3; printf("主线程%lu:\n",pthread_self()); int flag1 = pthread_create(&pthread_id1,NULL,function1,NULL); int flag2 = pthread_create(&pthread_id2,NULL,function2,NULL); int flag3 = pthread_create(&pthread_id3,NULL,function3,NULL); pthread_exit(0); return 0; }
你会发现170出现了两次,明明i是共享资源区的数据,为什么会出现两次呢???
那是因为对i++过程:读取、改值、写入(非原子操作---应用层)
这就会造成线程1可能正在对i进行读操作,线程2可能对i进行改值操作,且在进行操作的时候数据是不可见的。进而使得多线程并发时是不稳定的
因此我们要保证线程在并发的时候保证在同一时刻只允许一个线程操作要保护的内容-----------这就是线程的同步保护(信号量、互斥锁、条件变量)
大致过程为:创建一种保护机制----在操作之前加保护-----操作完后解保护---删除保护机制
1).互斥锁
创建互斥锁:
函数原型 :int pthread_mutex_init(pthread_mutex_t *id,const pthread_mutexattr_t *restrict attr);
函数参数;
pthread_mutex_t *id:创建的互斥锁编号
const pthread_mutexattr_t *restrict attr:互斥锁的属性,使用NULL表示缺省值。
函数返回值:成功返回0,失败返回-1,并且设置errno变量来指示错误的发生。
静态初始化互斥锁:pthread_mutex_t 变量名 = PTHREAD_MUTEX_INITIALIZER
注:动态创建的互斥锁在所有线程中都可以使用(常用方式),静态创建的互斥锁只能在本线程中使用
互斥锁加锁:
函数原型:int pthread_mutex_lock(pthread_mutex_t *mutex);
函数功能:对互斥锁进行加锁操作。
函数参数:
pthread_mutex_t *restrict mutex:具体需要加锁操作的互斥锁。
函数返回值:成功返回0,失败返回-1,并且设置errno变量来指示错误的发生
互斥锁解锁:
函数原型:int pthread_mutex_unlock(pthread_mutex_t *mutex);
函数功能:对互斥锁进行解锁操作。
函数参数:
pthread_mutex_t *restrict mutex:具体需要解锁操作的互斥锁。
函数返回值:成功返回0,失败返回-1,并且设置errno变量来指示错误的发生。
互斥锁删除:
函数原型:int pthread_mutex_destroy(pthread_mutex_t *mutex);
函数功能:对互斥锁进行销毁操作。
函数参数:
pthread_mutex_t *restrict mutex:具体需要解锁操作的互斥锁。
函数返回值:成功返回0,失败返回-1,并且设置errno变量来指示错误的发生。
实例:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
pthread_mutex_t ID;
//票数
int num =100000;
//线程函数
void * Pthread_function1(void * arg)
{
while(1)
{
//加锁
pthread_mutex_lock(&ID);
if(num > 0)
{
num--;
printf("1号卖票成功%d\n",num);
}
else
{
//解锁
pthread_mutex_unlock(&ID);
continue;
}
//解锁
pthread_mutex_unlock(&ID);
usleep(10);
}
}
void * Pthread_function2(void * arg)
{
while(1)
{
//加锁
pthread_mutex_lock(&ID);
if(num > 0)
{
num--;
printf("2号卖票成功%d\n",num);
}
else
{
//解锁
pthread_mutex_unlock(&ID);
continue;
}
//解锁
pthread_mutex_unlock(&ID);
usleep(10);
}
}
void * Pthread_function3(void * arg)
{
while(1)
{
//加锁
pthread_mutex_lock(&ID);
if(num > 0)
{
num--;
printf("3号卖票成功%d\n",num);
}
else
{
//解锁
pthread_mutex_unlock(&ID);
continue;
}
//解锁
pthread_mutex_unlock(&ID);
usleep(10);
}
}
int main()
{
//创建锁
pthread_mutex_init(&ID,NULL);
pthread_t pthread_id1;
pthread_create(&pthread_id1,NULL,Pthread_function1,(void*)"1号线程");
pthread_t pthread_id2;
pthread_create(&pthread_id2,NULL,Pthread_function2,(void*)"2号线程");
pthread_t pthread_id3;
pthread_create(&pthread_id3,NULL,Pthread_function3,(void*)"3号线程");
pthread_join(pthread_id1,NULL);
pthread_join(pthread_id2,NULL);
pthread_join(pthread_id3,NULL);
//销锁
pthread_mutex_destroy(&ID);
return 0;
}
2).条件变量
创建条件变量
函数原型:int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
函数功能:动态创建一个条件变量,并对其初始化。
函数参数:
pthread_cond_t *restrict cond:存放创建得到的条件变量编号,需要自定义这个变量。
const pthread_mutex_t *restrict mutex:条件变量的属性,使用NULL表示缺省值。
函数返回值:成功返回0,失败返回-1,并且设置errno变量来指示错误的发生。 静态初始化条件变量:pthread_cond_t 变量名 = PTHREAD_COND_INITIALIZER
激活条件变量:
函数原型:int pthread_cond_signal(pthread_cond_t *cond);
函数功能:发送信号,发送一个信号给另外一个正在处于阻塞等待状态的线程,使其脱离阻塞状态,继续执行.如果没有线程处在阻塞等待状态,pthread_cond_signal也会成功返回。
*注:该函数有阻塞功能。
函数参数:
a) pthread_cond_t *cond:要激活的具体条件变量(条件变量的编号值)
函数返回值:成功返回0,失败返回-1,并且设置errno变量来指示错误的发生。
等待条件变量:
函数头文件:#include <pthread.h>
函数原型:int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
函数功能:无条件阻塞等待条件变量,直至满足互斥条件为止。
函数参数:
a) pthread_cond_t *restrict cond:要等待的具体条件变量(条件变量的编号值)。
b) pthread_mutex_t *restrict mutex:互斥锁。
函数返回值:成功返回0,失败返回-1,并且设置errno变量来指示错误的发生。
*注:无论使用哪种等待方式,都必须和一个互斥锁配合,以防止多个线程同时请求。并且在调用“pthread_cond_wait”函数之前,必须由本线程使用“pthread_mutex_lock”函数加锁,而调用“pthread_cond_wait”函数会解锁,然后将线程挂起阻塞。直到条件被“pthread_cond_signal”函数激活,再将锁状态恢复为锁定状态,最后再用 pthread_mutex_unlock 进行解锁)。
删除条件变量:
函数头文件:#include <pthread.h>
函数原型:int pthread_cond_destroy(pthread_cond_t *cond);
函数功能:注销关闭指定的条件变量。
函数参数:
a) pthread_cond_t *cond:需要销毁关闭的条件变量。
函数返回值:成功返回0,失败返回-1,并且设置errno变量来指示错误的发生。
注:只有在没有线程在该条件变量上等待的时候,才能注销关闭该条件变量。
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
pthread_mutex_t ID;
pthread_cond_t cond_id;
//票数
int num =10;
//线程函数
void * Pthread_function1(void * arg)
{
while(1)
{
//加锁
pthread_mutex_lock(&ID);
if(num > 0)
{
num--;
printf("1号卖票成功%d\n",num);
}
else
{
pthread_cond_signal(&cond_id);
//解锁
pthread_mutex_unlock(&ID);
continue;
}
//解锁
pthread_mutex_unlock(&ID);
usleep(10);
}
}
void * Pthread_function2(void * arg)
{
while(1)
{
//加锁
pthread_mutex_lock(&ID);
if(num > 0)
{
num--;
printf("2号卖票成功%d\n",num);
}
else
{
pthread_cond_signal(&cond_id);
//解锁
pthread_mutex_unlock(&ID);
continue;
}
//解锁
pthread_mutex_unlock(&ID);
usleep(10);
}
}
void * Pthread_function3(void * arg)
{
while(1)
{
//加锁
pthread_mutex_lock(&ID);
if(num > 0)
{
num--;
printf("3号卖票成功%d\n",num);
}
else
{
pthread_cond_signal(&cond_id);
//解锁
pthread_mutex_unlock(&ID);
continue;
}
//解锁
pthread_mutex_unlock(&ID);
usleep(10);
}
}
void *Pthread_function4(void *arg)
{
while(1)
{
printf("枷锁\n");
pthread_mutex_lock(&ID);//枷锁
printf("阻塞\n");
pthread_cond_wait(&cond_id,&ID);//先解锁阻塞等待信号条件到达,条件到达后枷锁
printf("枷锁 阻塞 解锁\n");
if(num <= 0)
{
num = 10;
printf("添加成功\n");
}
pthread_mutex_unlock(&ID);
usleep(10);
}
}
int main()
{
//创建锁
pthread_mutex_init(&ID,NULL);
//创建条件变量
pthread_cond_init(&cond_id,NULL);
pthread_t pthread_id1;
pthread_create(&pthread_id1,NULL,Pthread_function1,(void*)"1号线程");
pthread_t pthread_id2;
pthread_create(&pthread_id2,NULL,Pthread_function2,(void*)"2号线程");
pthread_t pthread_id3;
pthread_create(&pthread_id3,NULL,Pthread_function3,(void*)"3号线程");
pthread_t pthread_id4;
pthread_create(&pthread_id4,NULL,Pthread_function4,(void*)"4号线程");
pthread_join(pthread_id1,NULL);
pthread_join(pthread_id2,NULL);
pthread_join(pthread_id3,NULL);
pthread_join(pthread_id4,NULL);
//销锁
pthread_mutex_destroy(&ID);
return 0;
}
3).信号灯
创建信号量
函数头文件:#include <semaphore.h>
函数原型:int sem_init(sem_t *sem, int pshared, unsigned int value);
函数功能:创建并初始化信号量
函数参数:
a) sem_t *sem:存放创建得到的信号量编号,需要自定义这个变量。
b) int pshared:信号量在作用范围(0:本进程中多个线程共享可用;非0:在当前登录用户的多个进程之间共享)。
c) unsigned int value:信号量的信号值。
函数返回值:成功返回0,失败返回-1,并且设置errno变量来指示错误的发生
信号量加保护
函数头文件:#include <semaphore.h>
函数原型:int sem_wait(sem_t * sem);
函数功能:消耗使用的信号,对信号量的值进行减1操作。
函数参数:
a) sem_t *sem:需要操作的具体信号量。
函数返回值:成功返回0,失败返回-1,并且设置errno变量来指示错误的发生。
信号量解保护
函数头文件:#include <semaphore.h>
函数原型:int sem_post(sem_t * sem);
函数功能:释放使用的信号,对信号量的值进行加1操作。
函数参数:
b) sem_t *sem:需要操作的具体信号量。
函数返回值:成功返回0,失败返回-1,并且设置errno变量来指示错误的发生。
信号值获取
函数头文件:#include <semaphore.h>
函数原型:int sem_getvalue(sem_t *sem, int *sval);
函数功能:获取指定信号量的当前信号值
函数参数:
a) sem_t *sem:需要操作的具体信号量。
b) int *sval:存放获取到的信号值。
函数返回值:成功返回0,失败返回-1,并且设置errno变量来指示错误的发生。
信号量删除
函数头文件:#include <semaphore.h>
函数原型:int sem_destroy(sem_t * sem);
函数功能:获取指定信号量的当前信号值
函数参数:
a) sem_t *sem:需要操作的具体信号量。
函数返回值:成功返回0,失败返回-1,并且设置errno变量来指示错误的发生。
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <semaphore.h>
sem_t sem_id;
//票数
int num =10000;
//消费者
void * Pthread_function1(void * arg)
{
while(1)
{
//V操作
sem_wait(&sem_id);
if(num > 0)
{
num--;
printf("1号卖票成功%d\n",num);
}
else
{
//P操作
sem_post(&sem_id);
continue;
}
//P操作
sem_post(&sem_id);
usleep(10);
}
}
//消费者
void * Pthread_function2(void * arg)
{
while(1)
{
//V操作
sem_wait(&sem_id);
if(num > 0)
{
num--;
printf("2号卖票成功%d\n",num);
}
else
{
//P操作
sem_post(&sem_id);
continue;
}
//P操作
sem_post(&sem_id);
usleep(10);
}
}
//消费者
void * Pthread_function3(void * arg)
{
while(1)
{
//V操作
sem_wait(&sem_id);
if(num > 0)
{
num--;
printf("3号卖票成功%d\n",num);
}
else
{
//P操作
sem_post(&sem_id);
continue;
}
//P操作
sem_post(&sem_id);
usleep(10);
}
}
int main()
{
//创建信号量
sem_init(&sem_id, 0, 1);
pthread_t pthread_id1;
pthread_create(&pthread_id1,NULL,Pthread_function1,(void*)"1号线程");
pthread_t pthread_id2;
pthread_create(&pthread_id2,NULL,Pthread_function2,(void*)"2号线程");
pthread_t pthread_id3;
pthread_create(&pthread_id3,NULL,Pthread_function3,(void*)"3号线程");
pthread_join(pthread_id1,NULL);
pthread_join(pthread_id2,NULL);
pthread_join(pthread_id3,NULL);
return 0;
}