进程有哪些缺陷?
1.创建的代价较高
进程是OS进行资源分配的基本单位,也就是说创建进程是需要分配资源的,所以创建代价很高
2.通信不方便
进程之间要想进行通信必须借助第三方资源(管道、内存映射、消息队列)
线程的优点有哪些?
线程与进行一样,在单核的情情况下都可以并发执行,多核可以并行执行。
但是线程的创建代价没有进程高,一个进程可以创建多个线程,这写线程共享同一份资源。共享同一份资源自然,通信就方便了很多
pthread_create函数
创建线程的函数
int pthread_create(pthread_t *thread,const pthread_attr_t *attr,(void *)( *start_routine)(void *),void *arg)
参一:传出参数,子线程tid
参二:线程的属性(attr)
参三:函数指针,子进程要执行的代码放在这个函数里面
参四:子进程函数的参数
返回值:
成功返回0
失败返回错误号
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
void* thread1(void* arg){
sleep(1);
printf("thread1 = %lu\n",pthread_self());
return NULL;
}
int main(int argc, char* argv[])
{
pthread_t tid;
int ret = pthread_create(&tid,NULL,thread1,NULL);
if(ret == -1){
perror("pthread_create error:");
return -1;
}
printf("main therad = %lu\n",pthread_self());
//%lu是无符号场整形 unsigned long int
while(1);
return 0;
}
pthread_self函数
获取线程tid的函数
pthread_t pthread_self(void)
返回值和无符号场整形,%lu
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<sys/stat.h>
int a;
void* thread1(void* arg){
printf("thread1 tid = %lu\n",pthread_self());
while(a!=10){
a++;
printf("a = %d\n",a);
sleep(1);
}
return NULL;
}
int main(int argc, char* argv[])
{
pthread_t tid;
int ret = pthread_create(&tid,NULL,thread1,NULL);
if(ret != 0){
perror("pthread_create error:");
return -1;
}
printf("main thread tid = %lu\n",pthread_self());
while(a!=10){
a++;
printf("a = %d\n",a);
sleep(1);
}
printf("pthread_create ret = %d\n",ret);
return 0;
}
终止线程的方法
1.进程结束了,底下的线程肯定都结束了
在主函数执行return
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<sys/stat.h>
int a;
void* thread1(void* arg){
while(a!=10){
a++;
printf("a = %d\n",a);
sleep(1);
}
return NULL;
}
int main(int argc, char* argv[])
{
pthread_t tid;
pthread_create(&tid,NULL,thread1,NULL);
sleep(1);
return 0;
}
调用exit()函数
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<stdlib.h>
int a;
void* thread1(void* arg){
while(a!=10){
a++;
printf("a = %d\n",a);
sleep(1);
}
return NULL;
}
void* thread2(void* arg){
sleep(2);
exit(-1);
return NULL;
}
int main(int argc, char* argv[])
{
pthread_t tid;
pthread_create(&tid,NULL,thread1,NULL);
pthread_create(&tid,NULL,thread2,NULL);
while(1);
return 0;
}
2.结束当前的线程
在start_rountine函数中执行return
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<stdlib.h>
int a;
void* thread1(void* arg){
while(a!=10){
a++;
printf("a = %d\n",a);
return NULL;
printf("1\n");
}
}
int main(int argc, char* argv[])
{
pthread_t tid;
pthread_create(&tid,NULL,thread1,NULL);
sleep(2);
return 0;
}
调用pthread_exit()函数
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<stdlib.h>
int a;
void func(){
printf("This is func!\n");
pthread_exit(NULL);
}
void* thread1(void* arg){
while(a!=10){
a++;
printf("a = %d\n",a);
func();
printf("1\n");
}
}
int main(int argc, char* argv[])
{
pthread_t tid;
pthread_create(&tid,NULL,thread1,NULL);
sleep(2);
return 0;
}
调用pthread_cancel函数
int pthread_cancel(pthread_t thread);
参一为要取消的线程
函数描述:调用该函数之后,会向指定线程发送一个取消请求,然后立即返回,被请求取消的进程需要等到某个取消点(通常为系统调用)
可以调用pthread_testcancel()函数,手动创建取消点
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<stdlib.h>
int a;
int stats;
void* thread1(void* arg){
while(1){
if(a == 10){
a=0;
}
a++;
pthread_testcancel();
}
}
int main(int argc, char* argv[])
{
pthread_t tid;
pthread_create(&tid,NULL,thread1,NULL);
int ret = pthread_cancel(tid);
if(ret == 0){
printf("cancel sucess!\n");
}else{
printf("cancel error!\n");
}
while(1){
printf("a = %d\n",a);
sleep(1);
}
return 0;
}
线程的连接和分离
pthread_join函数
函数原型:int pthread_join(pthread_t tid ,void **retval);
函数描述:等待指定线程的结束,然后回收,这种操作被称为连接。未连接的进程会产生僵尸线程
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<stdlib.h>
int a;
void* thread1(void* arg){
while(1){
if(a == 10){
pthread_exit(NULL);
}
a++;
}
}
int main(int argc, char* argv[])
{
pthread_t tid;
void* res;
pthread_create(&tid,NULL,thread1,NULL);
pthread_join(tid,&res);
printf("res = %s",(char*)res);
printf("a = %d\n",a);
return 0;
}
有些线程不关心返回状态,只是希望OS能在线程终止的时候自动清理并移出,这时可以调用pthread_detach函数设置线程未分离状态
pthread_detach函数
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<stdlib.h>
int a;
void* thread1(void* arg){
while(1){
if(a == 10){
pthread_exit(NULL);
}
a++;
}
}
void* thread2(void* arg){
while(1){
if(a == 10){
pthread_exit(NULL);
}
a++;
}
}
int main(int argc, char* argv[])
{
pthread_t tid1;
pthread_t tid2;
void* res;
pthread_create(&tid1,NULL,thread1,NULL);
pthread_create(&tid2,NULL,thread2,NULL);
pthread_detach(tid2);
pthread_join(tid2,&res);
printf("res = %s",(char*)res);
printf("a = %d\n",a);
return 0;
}
练习
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<stdlib.h>
const int N=1e6;
int a;
void* thread1(void* arg){
for(int i = 1;i <= N; i++){
a++;
}
}
int main(int argc, char* argv[])
{
pthread_t tids[10];
//创建10个线程,用tids数组存储tid
for(int i = 0;i < 10; i++){
pthread_create(tids+i,NULL,thread1,NULL);
}
//阻塞等待10个线程都执行结束,打印a的值
for(int i = 0;i < 10; i++){
pthread_join(tids[i],NULL);
}
printf("a = %d\n",a);
return 0;
}
观察数据
线程同步的引入
观察上面的数据,发现10个线程如果同时执行1e6次自增操作,最后结果应该是1e7,但是多次得到的结果确实小于等于1e7,这种情况是不可以接受的,为什么会出现这种问题?
因为线程之间的并发执行的,且a++不是原子操作。
举一个例子,现在线程1和线程2并发执行,它们同时读取了a的数据,读到的都是5,然后对5+1返回结果给a,线程1和线程2同时结束,a的值为6,而不是7,这就出现了问题。出现这个问题的原因是线程之间是共享资源的,同时访问一块内存,有时会造成错误。(保证线程和线程访问同一块空间的时候不出现冲突)
所以得让进程之间商量着来,协同一下步调。
同步
同步分为同步异步的同步和线程同步
同步与异步
提到同步和异步,很难不想到阻塞和非阻塞。
阻塞和非阻塞一半表述进程的状态,具体指的是进程在执行过程中是否遇到阻塞实现(是否被挂起),如果被挂起了就是阻塞状态,没有被挂起就是非阻塞
而同步异步一般指的是系统调用时候是否会阻塞执行
同步函数
同步函数是指在系统调用的过程中,程序会被阻塞,知道系统调用结束返回结果。
特点:
1.阻塞执行
2.可预测,符合传统程序的执行过程
异步函数
异步函数指的是系统调用过程中,程序不会阻塞,继续执行后面的代码。等到系统调用结束之后,内核会通过某种机制(信号或者回调函数等等)通知程序。
特点:
1.非阻塞:不会阻塞等待返回的结果,而是直接执行后面的代码。
2.事件驱动性:当系统调用完成时,内核会通过事件通知应用程序,应用程序需要处理这些事件
3.复杂性:异步编程模型复杂,需要处理多个事件和状态