线程
线程的概念
- 每个用户进程有自己的虚拟地址空间。
- 系统为每个用户进程创建一个task_struct 来描述该进程 struct task_struct。
- task_struct 和地址空间映射表一起用来表示一个进程。
- 由于进程的虚拟地址空间是私有的,因此进程切换开销很大。
- 为了提高系统的性能,linux引入轻量级进程, 起名为线程。
- 在同一个进程中创建的线程共享该进程的地址空间。
- Linux里同样用task_struct来描述一个线程
总结:
- 通常线程指的是共享相同虚拟地址空间的多个任务。
- 使用多线程, 大大提高了任务切换的效率。
注意:
1.每个进程中至少有一个线程,就是主线程,还可以产生多个线程共享4G内存空间,线程切换只需要虚拟CPU(寄存器)。
2.同样用task_struct来描述一个线程,线程和进程都参于统一的调度。
3.进程代表资源分配的最小单位,线程是最小的调度单位。
线程的相关函数
1.线程的创建
头文件:#include <pthread.h>
定义一个:pthread_t tid; //线程号
函数原型:int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
功能:创建线程
参数:
thread: &tid 线程号的地址
attr: NULL 线程函数默认的属性
start_routine: 线程函数的函数名
arg: 给线程函数传递的参数 NULL
返回值:
成功: 0
失败: -1
gcc编译时,记得在末尾加上 -pthread。
void *fun1(void *arg)
{
while(1)
{
printf("fun1 is running!\n");
sleep(2);
}
}
void *fun2(void *arg)
{
while(1)
{
printf("fun2 is running!\n");
sleep(2);
}
}
int main(int argc, const char *argv[])
{
pthread_t tid1,tid2; //线程号
int ret = pthread_create(&tid1, NULL, fun1, NULL);
if(ret == -1){
perror("pthread_create");
return -1;
}
ret = pthread_create(&tid2, NULL, fun2, NULL);
if(ret == -1){
perror("pthread_create");
return -1;
}
while(1)
{
printf("main is running!\n");
sleep(2);
}
return 0;
}
结果会每隔1秒交替打印:
main is running!
fun1 is running!
fun2 is running!
2.回收线程资源
函数原型:int pthread_join(pthread_t thread, void **retval);
功能:主线程等待子线程结束,然后回收资源
重点:阻塞
参数:
thread: tid 线程号
retval: NULL (接收子线程结束的返回值)
返回值:
成功:0
失败:-1
我们来看下它是如何阻塞的:
void *fun(void *arg)
{
int n = 3;
while(n--)
{
printf("fun is running\n");
sleep(1);
}
}
int main(int argc, char *argv[])
{
pthread_t tid;
int ret = pthread_create(&tid,NULL,fun,NULL);
if (-1 == ret)
{
perror("pthread_create");
return -1;
}
printf("text1---------\n");
pthread_join(tid,NULL);
printf("text2---------\n");
while(1)
{
printf("main is running\n");
sleep (1);
}
return 0;
}
结果为:
text1---------
fun is running
fun is running
fun is running
text2---------
main is running
。。。。。。。
text1打印出来后阻塞着,仅子线程运行。阻塞3秒以后text2打印出来,进入主线程无限循环。
函数原型:int pthread_detach(pthread_t thread);
功能:主线程和子线程分离开来,系统自动回收资源
重点:非阻塞
参数:
thread: tid 线程号
返回值:
成功:0
失败: -1
我们来看下这个非阻塞函数:
void *fun(void *arg)
{
int n = 3;
while(n--)
{
printf("fun is running\n");
sleep(1);
}
}
int main(int argc, char *argv[])
{
pthread_t tid;
int ret = pthread_create(&tid,NULL,fun,NULL);
if (-1 == ret)
{
perror("pthread_create");
return -1;
}
printf("text1---------\n");
pthread_detach(tid);
printf("text2---------\n");
while(1)
{
printf("main is running\n");
sleep (1);
}
return 0;
}
结果为:
text1---------
text2---------
main is running
fun is running
main is running
fun is running
main is running
fun is running
main is running
main is running
text1和text2都打印出来,一起运行3秒以后,系统自动回收子线程资源,进入主线程无限循环。
3.结束子线程
函数原型:void pthread_exit(void *retval);
功能:结束子线程
参数:
retval : void *
返回值:
成功:
失败:
void *fun(void *arg)
{
int n = *(int *)arg;//强转下类型接收传递的参数
printf("n=%d\n",n);
pthread_exit(arg);//结束后将参数返回
}
int main(int argc, char *argv[])
{
int m = 66;
pthread_t tid;
int ret = pthread_create(&tid,NULL,fun,(void *)&m);//将参数传给子线程fun
if (-1 == ret)
{
perror("pthread_create");
return -1;
}
void *a;//定义一个指针来接收exit的返回值
pthread_join(tid,&a);//该函数的第二个参数就是接收exit的返回值
printf("a= %d\n",*(int *)a);//打印接收的返回值
sleep (1);
return 0;
}
结果为:
n=66
a=66
线程的同步和互斥
线程的有点和缺点:
优点:线程间很容易进行通信,通过全局变量实现数据共享和交换。
缺点:多个线程同时访问共享对象时产生竟态,需要引入同步和互斥机制。
同步和互斥 :为了保护共享资源,避免竟态。
同步:多个任务按理想的顺序/步调来进行访问。
互斥:使多个任务不能同时访问。
1.线程的同步—信号量函数
头文件:#include <semaphore.h>
定义一个:sem_t sem; //信号量的变量
函数原型:int sem_init(sem_t *sem, int pshared, unsigned int value);
功能:初始化信号量
参数:
sem: &sem
pshared: 0:线程 1:进程
value: 初值 0 or 1
返回值:
成功:0
失败:-1
int sem_wait(sem_t *sem); //如果初值是0,阻塞函数,如果初值是1,非阻塞,运行后将初值减1,变为阻塞状态。
int sem_post(sem_t *sem); //加1函数,使前面阻塞的初值变为1,从而不阻塞。
函数简易模型为:
sem_t sem1,sem2;//定义两个信号量变量
初始化:
sem_init(&sem1,0,0);
sem_init(&sem2,0,1);
初始化时一个初值给1一个初值给0
sem_wait(&sem2);sem2初值为1,非阻塞,运行后sem2值为0,阻塞。
需要先运行的线程
sem_post(&sem1);sem1初值为0,到这里sem1值变为1。
sem_wait(&sem1);sem1值变为1后,非阻塞,运行后sem1值变为0,阻塞。
需要后运行的线程
sem_post(&sem2);sem2运行后值为0,运行到这里时,值变为1,返回上面需要先运行线程,进入循环。
2.线程的互斥------互斥锁
定义一个:pthread_mutex_t mutex; //互斥锁的变量
函数原型:int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);
功能:初始化互斥锁
参数:
mutex: &mutex
mutexattr: NULL (默认属性)
返回值:
成功:0
失败:-1
int pthread_mutex_lock(pthread_mutex_t *mutex);
//加锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
//解锁
pthread_t tid1,tid2;
pthread_mutex_t mutex;//互斥锁变量
int i,value1, value2; //全局变量
void *fun1(void *arg)
{
while(1)
{
pthread_mutex_lock(&mutex);
i++;
value1 = i;
value2 = i;
pthread_mutex_unlock(&mutex);
}
}
void *fun2(void *arg)
{
while(1)
{
pthread_mutex_lock(&mutex);
if(value1 != value2)
{
printf("%d %d %d\n", i, value1, value2);
}
pthread_mutex_unlock(&mutex);
}
}
int main(int argc, const char *argv[])
{
pthread_mutex_init(&mutex, NULL); //初始化互斥锁
pthread_create(&tid1, NULL, fun1, NULL);
pthread_detach(tid1);
pthread_create(&tid2, NULL, fun2, NULL);
pthread_detach(tid2);
while(1);
return 0;
}
不加锁时,会进行打印。
加锁后,保证读取时都是相同的数,不会打印。
下一篇为进程间通信的知识点总结。。。。