欢迎大家来到IT世界,在知识的湖畔探索吧!
创建线程函数pthread_create()和等待线程函数pthread_join()的用法。
注意:在创建线程pthread_create()之前,要先定义线程标识符:
pthread_t 自定义线程名;
【例子1】创建线程以及等待线程执行完毕。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
//线程要运行的函数,除了函数名myfunc,其他全都是固定的。
void* myfunc()
{
printf(“Hello World!\n”);
return NULL;
}
int main()
{
pthread_t th;//在创建线程之前要先定义线程标识符th,相当于int a这样
pthread_create(&th,NULL,myfunc,NULL);
/*第一个参数是要创建的线程的地址
第二个参数是要创建的这个线程的属性,一般为NULL
第三个参数是这条线程要运行的函数名
第四个参数三这条线程要运行的函数的参数*/
pthread_join(th,NULL);
/*线程等待函数,等待子线程都结束之后,整个程序才能结束
第一个参数是子线程标识符,第二个参数是用户定义的指针用来存储线程结束时的返回值*/
return 0;
}
//编译运行多线程的程序,要在gcc命令尾部加上-lpthread
【例子2】
我们想看看哪些数字是第一条线程打印出来的,哪些数字是第二条线程打印出来的。
可以通过传递参数的方法来查看。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
void* myfunc(void* args)
{
int i;
//由于“th1”是字符串,所以这里我们要做个强制转换,把void*强制转换为char*
char* name = (char*) args;
for(i=1;i<50;i++)
{
printf(“%s:%d\n”,name,i);
}
return NULL;
}
int main()
{
pthread_t th1;
pthread_t th2;
pthread_create(&th1,NULL,myfunc,”th1″);//pthread_create的第四个参数是要执行的函数的参数哦!~
//这里的“th1”就是void* args
pthread_create(&th2,NULL,myfunc,”th2″);
pthread_join(th1,NULL);
pthread_join(th2,NULL);
return 0;
}
运行之后可以看到哪些数字是th1打印的,哪些数字是th2打印的。
root@ubuntu:/home/vico/Desktop/20230314# ./ls
th2:1
th2:2
th2:3
th2:4
th2:5
th2:6
th2:7
th2:8
th2:9
th2:10
th2:11
th2:12
th2:13
th2:14
th2:15
th2:16
th2:17
th2:18
th2:19
th2:20
th2:21
th2:22
th2:23
th2:24
th2:25
th2:26
th2:27
th2:28
th2:29
th2:30
th2:31
th2:32
th2:33
th2:34
th2:35
th2:36
th2:37
th2:38
th2:39
th2:40
th2:41
th2:42
th2:43
th2:44
th2:45
th2:46
th2:47
th2:48
th2:49
th1:1
th1:2
th1:3
th1:4
th1:5
th1:6
th1:7
th1:8
th1:9
th1:10
th1:11
th1:12
th1:13
th1:14
th1:15
th1:16
th1:17
th1:18
th1:19
th1:20
th1:21
th1:22
th1:23
th1:24
th1:25
th1:26
th1:27
th1:28
th1:29
th1:30
th1:31
th1:32
th1:33
th1:34
th1:35
th1:36
th1:37
th1:38
th1:39
th1:40
th1:41
th1:42
th1:43
th1:44
th1:45
th1:46
th1:47
th1:48
th1:49
root@ubuntu:/home/vico/Desktop/20230314#
【例子3】
定义一个大小为5000的数组,随机生成5000个数,我们想创建两条线程,让这两条线程去计算这5000个数字的和,第一条线程计算前2500个数的和,第二条线程让它算后2500个数字的和。怎么做?
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
int arr[5000];
int s1=0;
int s2=0;
void* myfunc1(void* args)
{
int i;
for(i=0;i<2500;i++)
{
s1 = s1 + arr[i];
}
return NULL;
}
void* myfunc2(void* args)
{
int i;
for(i=2500;i<5000;i++)
{
s2 = s2 + arr[i];
}
return NULL;
}
int main()
{
//初始化数组
int i;
for(i=0;i<5000;i++)
{
arr[i] = rand() % 50;
}
/* for(i=0;i<5000;i++)
{
printf(“a[%d]=%d\n”,i,arr[i]);
}*/
pthread_t th1;
pthread_t th2;
pthread_create(&th1,NULL,myfunc1,NULL);
pthread_create(&th2,NULL,myfunc2,NULL);
pthread_join(th1,NULL);
pthread_join(th2,NULL);
printf(“s1=%d\n”,s1);
printf(“s2=%d\n”,s2);
printf(“s1+s2=%d\n”,s1+s2);
return 0;
}
运行结果:
【例子4】
上一个例子的代码重复率太高,我们对其优化,加入了结构体,也只用了同一个函数。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
int arr[5000];
int s = 0;
typedef struct {
int first;
int last;
}MY_ARGS;
void* myfunc(void* args)
{
int i;
MY_ARGS* my_args = (MY_ARGS*)args;
int first = my_args->first;
int last = my_args->last;
for(i=first;i<last;i++)
{
s = s + arr[i];
}
printf(“s = %d\n”,s);
s=0;
return NULL;
}
int main()
{
//初始化数组
int i;
for(i=0;i<5000;i++)
{
arr[i] = rand() % 50;
}
/* for(i=0;i<5000;i++)
{
printf(“a[%d]=%d\n”,i,arr[i]);
}*/
pthread_t th1;
pthread_t th2;
//设置两个结构体变量作为参数
MY_ARGS args1 = {0,2500};
MY_ARGS args2 = {2500,5000};
pthread_create(&th1,NULL,myfunc,&args1);
pthread_create(&th2,NULL,myfunc,&args2);
pthread_join(th1,NULL);
pthread_join(th2,NULL);
return 0;
}
运行结果:
【例子5】
来看看如果把s加在全局变量,让s++循环10000次后会发生什么?
我们发现每次执行后的s都不一样,按理说s应该是200000才对呀,为什么会这样呢?
因为在第一条线程读s并s++的时候,第二条线程也会来读,可能在第一条线程进行加之前读也可能在加之后读,所以我们会丢失一些s++,所以每次运行出来的结果都不一样。
这种情况叫做race condition,当出现race contion的时候,就很有可能会出现错误的结果。
那么要如何解决race condition呢?
最常用的方法就是加锁。
有一种锁叫mutex。
我们看看mutex要怎么用?
pthread_mutex_t lock;
这种pthread_mutex_t的数据类型叫锁
定义一个锁后要对锁进行初始化
pthread_mutex_init(&lock,NULL);
锁初始化函数有两个参数,第一个参数就是我们定义的锁,第二个参数是互斥锁的属性,写NULL就可以了,代表默认的快速互斥锁。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
pthread_mutex_t lock;//定义一个互斥锁
int s = 0;
void* myfunc(void* args)
{
int i = 0;
pthread_mutex_lock(&lock);//这个函数表示,在这个地方上一个锁,就是摆一个锁在这个地方
for(i=0;i<100000;i++)
{
s++;
}
pthread_mutex_unlock(&lock);//把这个锁给解掉
return NULL;
}
int main()
{
pthread_t th1;
pthread_t th2;
pthread_mutex_init(&lock,NULL);//初始化这个锁,此时只是创建了这个锁而已,还没有加进去哦。
/*锁不是用来锁一个变量,它是用来锁住一段代码的。*/
pthread_create(&th1,NULL,myfunc,NULL);
pthread_create(&th2,NULL,myfunc,NULL);
pthread_join(th1,NULL);
pthread_join(th2,NULL);
printf(“s = %d\n”,s);
return 0;
}
运行结果:
解释一下上图的结果,加了锁之后得到的结果就是正确的了,第一次运行我是把锁加在for循环里头,可以看到运行的时间是.0.01ms是很慢的,而第二次运行也就是把锁加在for循环的外头,可以看到速度就快多了,所以加锁的位置很重要,最好不要加在循环里面,不然要一直循环开锁解锁就特别慢。
讲一下两条线程是遇到这个加锁的代码是怎么做的,
两条线程看谁先抢到这个锁,也是竞争在抢锁,如果是th1先抢到,那锁就是th1的了,拿到锁的线程就很自私,接下来锁里面的代码就是th1自己一个人的,th2就不能来读这段代码了,th2没抢到锁的话它自己是不会去自己加个锁的,th2只能靠边站了,等th1先走完了锁里的代码,然后解锁了,再轮th2,加锁可以保证两条线程不会去抢着读数据,导致结果出错。
加了锁,多线程就变成了两个单线程按顺序串行着走完,两个for循环是独立存在的。
互斥锁的作用:
互斥锁可以 防止两条线程竞争共享数据资源而引起的与时间上有关的数据混乱。
每个线程在对共享资源操作前都尝试先加锁,成功加锁才能操作,操作结束解锁。
但通过“锁”就将资源的访问变成互斥操作,而后与时间有关的错误也不会再产生了。
在访问共享资源前加锁,访问结束后立即解锁。锁的“粒度”应越小越好。
多核的假共享的概念False sharing
为了避免假共享,最好分别把记录的结果当成局部变量。
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://itzsg.com/34733.html