线程的概念
进程是一条正在运行的程序
线程是进程内部的一条执行序列或执行路径,一个进程可以包含多条线程。
在操作系统中,线程的实现有以下三种方式
内核级线程
用户级线程
组合级线程
Linux 中线程的实现
Linux 实现线程的机制非常独特。从内核的角度来说,它并没有线程这个概念。Linux 把所有的线程都当做进程来实现。内核并没有准备特别的调度算法或是定义特别的数据结构来表征线程。相反,线程仅仅被视为一个与其他进程共享某些资源的进程。每个线程都拥有唯一隶属于自己的 task_struct,所以在内核中,它看起来就像是一个普的进程(只是线程和其他一些进程共享某些资源,如地址空间)。
进程与线程的区别
1、进程是资源分配的最小单位,线程是 CPU 调度的最小单位
2、进程有自己的独立地址空间,线程共享进程中的地址空间
3、进程的创建消耗资源大,线程的创建相对较小
4、进程的切换开销大,线程的切换开销相对较小
目前层面,C语言没有提供多线程,C++现在新标准提了创建线程的方法
Linux线程库中的接口介绍
#include <pthread.h>
/*
pthread_create()用于创建线程
thread: 接收创建的线程的 ID
attr: 指定线程的属性
start_routine: 指定线程函数
arg: 给线程函数传递的参数
成功返回 0, 失败返回错误码
*/
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
/*
pthread_exit()退出线程
retval:指定退出信息
*/
int pthread_exit(void *retval);
/*
pthread_join()等待 thread 指定的线程退出,线程未退出时,该方法阻塞
retval:接收 thread 线程退出时,指定的退出信息
*/
int pthread_join(pthread_t thread, void **retval);
Linux多线程示例
1、
执行
再执行一次
这种情况是主线程执行完了,直接退出主函数了,执行exit退出整个进程,连同整个线程直接退出了,有的线程还没执行完就 退出,一般让主线程最后再退出。
修改代码
执行
线程并发运行,fun和main谁先打印系统决定
再调整一下
执行
再调整一下
执行
为什么?因为主线程只有2次,没事干了,系统会帮助调动exit退出进程,整个线程就异常结束
调整一下让主线程退出
执行
调整一下
等待fun结束
使用字符串常量是因为线程结束临时变量回消失。试图获得临时变量的地址应用字符串常量要注意生存期!
执行
下面这个操作也可以
注意:create语句执行了,fun线程才启动
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <pthread.h>
void * pthread_fun(void *arg)
{
int i = 0;
for(; i < 5; ++i)
{
sleep(1);
printf("fun thread running\n");
}
pthread_exit("fun over");
}
int main()
{
pthread_t tid;
int res = pthread_create(&tid, NULL, pthread_fun, NULL);
assert(res == 0);
int i = 0;
for(; i < 5; ++i)
{
sleep(1);
printf("main thread running\n");
}
char *s = NULL;
pthread_join(tid, (void **)&s);
printf("s = %s\n", s);
exit(0);
}
线程并发运行
打印结果
通过指针对i间接引用获取,执行第10行的那一刻,i的值是多少,index就是多少。create把i的地址传进去,地址是不会变的,但是通过i的地址获取i的值,fun函数什么时候走到获取i的值,才获取i的值,主线程i的值一直在循环++
比如说,当前启动第一个线程,当这个线程去获取i的值,原本把i的地址传进去,i为0,以为fun获取的值就为0,但是有可能fun线程走的慢一点,去获取i的值的时候,i的值已经变成1了,启动是有一个过程,主线程的++循环并没有停止,也有可能已经加到2了。
i的值在主线程周期性的改变
最后在27行的时候改为0,阻塞大概3秒钟,然后执行i++,
两个0,是一开始执行慢了。走到27行把i置为0,才获取到0
当i为0,只有一个线程被创建,i变为1后,才有第2个线程被创建,0是其中一个线程运行的比较晚,到27行被加到0的两个0有可能是27行时两个线程同时获取0,也有可能第一个线程运行特别快获取到0了。
i没有++,第二个线程是不会被创建的
i++,启动了多个线程,但是线程对i的获取时间不同,造成值不一样了。
多个线程是同时进行,create启动一次,就启动了一个线程。create 5次,就启动了5个线程,是并发运行的
执行
示例代码1
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
void* thread_fun(void *arg)
{
int index = *((int*)arg);
int i = 0;
for( ;i < 5; i++ )
{
printf("index=%d\n",index);
sleep(1);
}
}
int main()
{
pthread_t id[5];
int i = 0;
for( ; i < 5; i++ )
{
pthread_create(&id[i], NULL, thread_fun, (void*)&i);
}
for( i = 0;i < 5; i++ )
{
pthread_join(id[i],NULL);
}
exit(0);
}
示例代码2
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>
int g = 0;
void* thread_fun(void *arg)
{
int i = 0;
for( ;i < 1000; i++ )
{
printf("g=%d\n", ++g);
}
}
int main()
{
pthread_t id[5];
int i = 0;
for( ; i < 5; i++ )
{
pthread_create(&id[i], NULL, thread_fun, NULL);
}
for( i = 0;i < 5; i++ )
{
pthread_join(id[i], NULL);
}
exit(0);
}
看例子
创建5个线程 并发运行 对全局变量++1000次
正常情况下一共对val加加了5000次
运行结果如下
再运行一次
这是为什么呢?
我们对val++,不是原子操作,转换为指令,有多条指令构成,计算机执行的是二进制的指令
我们对变量的改变分了很多步骤
比如有两条线程对val++
但是++不是一下子可以完成,先将val读过来,再++,再读回去,这个操作还没结束,另外一个线程也把val读过来,++,再读回去。有可能两个线程对val=1;进行加加,最后值却为2
通过互斥锁,信号量进行保护
出现这种情况,其中还有一个问题。
对虚拟机的设置做一点改变
重新启动,对刚才的程序再执行
运行二十多次,出现1次小于5000的情况
大概率都是出现5000了
这是为什么呢?
刚才有4个处理器,至少有两个处理器有处理其他线程,存在一个线程放在2个处理器上,同时访问,出现小于5000的概率比较高,并行引起的
调成1个处理器,5个线程,1个线程执行,其余4个肯定没有执行,不出现同时执行两个线程的情况。出现小于5000的概率很小(这个原因是,把val值1读过来,还没来得及++回去,这个时候时间片到了,发生了切换,换到其余线程,读过来还是1,加加,现场恢复,还是1进行加加,这种场景出现的概率非常小)
1个处理器不能并行的