C语言多线程教程(pthread)(线程创建pthread_t,指定线程run方法pthread_create()函数,加mutex锁,解锁,伪共享 false sharing【假共享】)

470 篇文章 71 订阅

[C语言]多线程程序入门教程

thread线程和process进程:前者共享内存,后者不共享

查看pthread_create()函数文档

man pthread_create
PTHREAD_CREATE(3)                                                                      Linux Programmer's Manual                                                                     PTHREAD_CREATE(3)

NAME
       pthread_create - create a new thread	//创建一个新线程

SYNOPSIS
       #include <pthread.h>

       int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);

       Compile and link with -pthread.

DESCRIPTION
		//注:第一个 参数负责向调用者传递子线程的线程号
		//我是在这看到的:https://www.jianshu.com/p/d16d0e1efc74


       The pthread_create() function starts a new thread in the calling process.  The new thread starts execution by invoking start_routine(); arg is passed as the sole argument of start_routine().
       //pthread_create() 函数在调用进程中启动一个新线程。 
       //新线程通过调用 start_routine() 开始执行; arg 作为 start_routine() 的唯一参数传递。

       The new thread terminates in one of the following ways:	
       //新线程以下列方式之一终止:

       * It calls pthread_exit(3), specifying an exit status value that is available to another thread in the same process that calls pthread_join(3).
       * //它调用 pthread_exit(3),指定一个退出状态值,该值可用于调用 pthread_join(3) 的同一进程中的另一个线程。

       * It returns from start_routine().  This is equivalent to calling pthread_exit(3) with the value supplied in the return statement.
       * //它从 start_routine() 返回。 这等效于使用 return 语句中提供的值调用 pthread_exit(3)。

       * It is canceled (see pthread_cancel(3)).
       * //它被取消(参见 pthread_cancel(3))。

       * Any of the threads in the process calls exit(3), or the main thread performs a return from main().  This causes the termination of all threads in the process.
       * //进程中的任何线程调用exit(3),或者主线程执行main() 的返回。 
       * //这会导致进程中的所有线程终止。

       The  attr  argument  points  to  a  pthread_attr_t  structure  whose  contents are used at thread creation time to determine attributes for the new thread; this structure is initialized using
       pthread_attr_init(3) and related functions.  If attr is NULL, then the thread is created with default attributes.
       //attr 参数指向一个 pthread_attr_t 结构,其内容在线程创建时用于确定新线程的属性; 
       //这个结构是使用 pthread_attr_init(3) 和相关函数初始化的。 
       //如果 attr 为 NULL,则使用默认属性创建线程。(attr是第二个参数)

       Before returning, a successful call to pthread_create() stores the ID of the new thread in the buffer pointed to by thread; this identifier is used to refer to the thread in subsequent  calls
       to other pthreads functions.
       //在返回之前,成功调用 pthread_create() 会将新线程的 ID 存储在 thread 指向的缓冲区中; 
       //此标识符用于在后续调用其他 pthreads 函数时引用线程。

       The  new  thread  inherits  a copy of the creating thread's signal mask (pthread_sigmask(3)).  The set of pending signals for the new thread is empty (sigpending(2)).  The new thread does not
       inherit the creating thread's alternate signal stack (sigaltstack(2)).
       //新线程继承创建线程的信号掩码 (pthread_sigmask(3)) 的副本。 
       //新线程的未决信号集为空(sigpending(2))。 
       //新线程不继承创建线程的备用信号堆栈 (sigaltstack(2))。

       The new thread inherits the calling thread's floating-point environment (fenv(3)).
       //新线程继承调用线程的浮点环境 (fenv(3))。

       The initial value of the new thread's CPU-time clock is 0 (see pthread_getcpuclockid(3)).
       //新线程的 CPU 时间时钟的初始值为 0(请参阅 pthread_getcpuclockid(3))。

   Linux-specific details
       The new thread inherits copies of the calling thread's capability sets (see capabilities(7)) and CPU affinity mask (see sched_setaffinity(2)).
       //新线程继承调用线程的功能集(请参阅功能(7))和 CPU 亲和掩码(请参阅 sched_setaffinity(2))的副本。

RETURN VALUE
       On success, pthread_create() returns 0; on error, it returns an error number, and the contents of *thread are undefined.
       //成功时,pthread_create() 返回 0; 出错时,它返回一个错误号,并且 *thread 的内容是未定义的。

ERRORS
       EAGAIN Insufficient resources to create another thread.
       //资源不足,无法创建另一个线程。

       EAGAIN A system-imposed limit on the number of threads was encountered.  There are a number of limits that may trigger this error: the RLIMIT_NPROC soft resource limit (set via setrlimit(2)),
              which  limits  the number of processes and threads for a real user ID, was reached; the kernel's system-wide limit on the number of processes and threads, /proc/sys/kernel/threads-max,
              was reached (see proc(5)); or the maximum number of PIDs, /proc/sys/kernel/pid_max, was reached (see proc(5)).
              //遇到系统对线程数施加的限制。 
              //有许多限制可能会触发此错误:已达到 RLIMIT_NPROC 软资源限制(通过 setrlimit(2) 设置),它限制了真实用户 ID 的进程和线程数; 
              //已达到内核对进程和线程数/proc/sys/kernel/threads-max 的系统范围限制(请参阅 proc(5)); 
              //或已达到最大 PID 数 /proc/sys/kernel/pid_max(请参阅 proc(5))。

       EINVAL Invalid settings in attr.
       //attr 中的设置无效。

       EPERM  No permission to set the scheduling policy and parameters specified in attr.
       //无权设置 attr 中指定的调度策略和参数。

ATTRIBUTES
       For an explanation of the terms used in this section, see attributes(7).

       ┌─────────────────┬───────────────┬─────────┐
       │Interface        │ Attribute     │ Value   │
       ├─────────────────┼───────────────┼─────────┤
       │pthread_create() │ Thread safety │ MT-Safe │
       └─────────────────┴───────────────┴─────────┘

CONFORMING TO
       POSIX.1-2001, POSIX.1-2008.

NOTES
       See pthread_self(3) for further information on the thread ID returned in *thread by pthread_create().  Unless real-time scheduling policies are being employed, after a  call  to  pthread_cre‐
       ate(), it is indeterminate which thread—the caller or the new thread—will next execute.
       //有关 pthread_create() 在 *thread 中返回的线程 ID 的更多信息,请参见 pthread_self(3)。 
       //除非采用实时调度策略,否则在调用 pthread_create() 之后,将不确定哪个线程(调用者或新线程)接下来将执行。

       A thread may either be joinable or detached.  If a thread is joinable, then another thread can call pthread_join(3) to wait for the thread to terminate and fetch its exit status.  Only when a
       terminated joinable thread has been joined are the last of its resources released back to the system.  When a detached thread terminates, its resources are automatically released back to  the
       system:  it  is not possible to join with the thread in order to obtain its exit status.  Making a thread detached is useful for some types of daemon threads whose exit status the application
       does not need to care about.  By default, a new thread is created in a joinable state, unless attr was set to create the thread in a detached state (using pthread_attr_setdetachstate(3)).
       //线程可以是可连接的或可分离的。 
       //如果一个线程是可连接的,那么另一个线程可以调用 pthread_join(3) 来等待线程终止并获取其退出状态。 
       //只有当一个终止的可连接线程被连接时,它的最后一个资源才会释放回系统。 
       //当一个分离的线程终止时,它的资源会自动释放回系统:不可能加入线程以获得它的退出状态。 
       //使线程分离对于应用程序不需要关心其退出状态的某些类型的守护线程很有用。 
       //默认情况下,新线程在可连接状态下创建,除非 attr 设置为在分离状态下创建线程(使用 pthread_attr_setdetachstate(3))。

       On Linux/x86-32, the default stack size for a new thread is 2 megabytes.  Under the NPTL threading implementation, if the RLIMIT_STACK soft resource limit at the time the program started  has
       any  value  other  than  "unlimited", then it determines the default stack size of new threads.  Using pthread_attr_setstacksize(3), the stack size attribute can be explicitly set in the attr
       argument used to create a thread, in order to obtain a stack size other than the default.
       //在 Linux/x86-32 上,新线程的默认堆栈大小为 2 兆字节。 
       //在 NPTL 线程实现下,如果程序启动时的 RLIMIT_STACK 软资源限制具有“unlimited”以外的任何值,则它决定了新线程的默认堆栈大小。 
       //使用 pthread_attr_setstacksize(3),可以在用于创建线程的 attr 参数中显式设置堆栈大小属性,以获得默认值以外的堆栈大小。

BUGS
       In the obsolete LinuxThreads implementation, each of the threads in a process has a different process ID.  This is in violation of the POSIX threads specification, and is the source  of  many
       other nonconformances to the standard; see pthreads(7).
       //在过时的 LinuxThreads 实现中,进程中的每个线程都有不同的进程 ID。 
       //这违反了 POSIX 线程规范,并且是许多其他不符合标准的根源; 参见 pthreads(7)。

EXAMPLE
       The program below demonstrates the use of pthread_create(), as well as a number of other functions in the pthreads API.
       //下面的程序演示了 pthread_create() 的使用,以及 pthreads API 中的许多其他函数。

       In the following run, on a system providing the NPTL threading implementation, the stack size defaults to the value given by the "stack size" resource limit:
       //在以下运行中,在提供 NPTL 线程实现的系统上,堆栈大小默认为“堆栈大小”资源限制给出的值:

           $ ulimit -s
           8192            # The stack size limit is 8 MB (0x800000 bytes)
           $ ./a.out hola salut servus
           Thread 1: top of stack near 0xb7dd03b8; argv_string=hola
           Thread 2: top of stack near 0xb75cf3b8; argv_string=salut
           Thread 3: top of stack near 0xb6dce3b8; argv_string=servus
           Joined with thread 1; returned value was HOLA
           Joined with thread 2; returned value was SALUT
           Joined with thread 3; returned value was SERVUS

       In the next run, the program explicitly sets a stack size of 1MB (using pthread_attr_setstacksize(3)) for the created threads:
       //在下一次运行中,程序为创建的线程显式设置了 1MB 的堆栈大小(使用 pthread_attr_setstacksize(3)):

           $ ./a.out -s 0x100000 hola salut servus
           Thread 1: top of stack near 0xb7d723b8; argv_string=hola
           Thread 2: top of stack near 0xb7c713b8; argv_string=salut
           Thread 3: top of stack near 0xb7b703b8; argv_string=servus
           Joined with thread 1; returned value was HOLA
           Joined with thread 2; returned value was SALUT
           Joined with thread 3; returned value was SERVUS

   Program source

       #include <pthread.h>
       #include <string.h>
       #include <stdio.h>
       #include <stdlib.h>
       #include <unistd.h>
       #include <errno.h>
       #include <ctype.h>

       #define handle_error_en(en, msg) \
               do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)

       #define handle_error(msg) \
               do { perror(msg); exit(EXIT_FAILURE); } while (0)

       struct thread_info {    /* Used as argument to thread_start() */
           pthread_t thread_id;        /* ID returned by pthread_create() */
           int       thread_num;       /* Application-defined thread # */
           char     *argv_string;      /* From command-line argument */
       };

       /* Thread start function: display address near top of our stack,
          and return upper-cased copy of argv_string */

       static void *
       thread_start(void *arg)
       {
           struct thread_info *tinfo = arg;
           char *uargv, *p;

           printf("Thread %d: top of stack near %p; argv_string=%s\n",
                   tinfo->thread_num, &p, tinfo->argv_string);

           uargv = strdup(tinfo->argv_string);
           if (uargv == NULL)
               handle_error("strdup");

           for (p = uargv; *p != '\0'; p++)
               *p = toupper(*p);

           return uargv;
       }

       int
       main(int argc, char *argv[])
       {
           int s, tnum, opt, num_threads;
           struct thread_info *tinfo;
           pthread_attr_t attr;
           int stack_size;
           void *res;

           /* The "-s" option specifies a stack size for our threads */

           stack_size = -1;
           while ((opt = getopt(argc, argv, "s:")) != -1) {
               switch (opt) {
               case 's':
                   stack_size = strtoul(optarg, NULL, 0);
                   break;

               default:
                   fprintf(stderr, "Usage: %s [-s stack-size] arg...\n",
                           argv[0]);
                   exit(EXIT_FAILURE);
               }
           }

           num_threads = argc - optind;

           /* Initialize thread creation attributes */

           s = pthread_attr_init(&attr);
           if (s != 0)
               handle_error_en(s, "pthread_attr_init");

           if (stack_size > 0) {
               s = pthread_attr_setstacksize(&attr, stack_size);
               if (s != 0)
                   handle_error_en(s, "pthread_attr_setstacksize");
           }

           /* Allocate memory for pthread_create() arguments */

           tinfo = calloc(num_threads, sizeof(struct thread_info));
           if (tinfo == NULL)
               handle_error("calloc");

           /* Create one thread for each command-line argument */

           for (tnum = 0; tnum < num_threads; tnum++) {
               tinfo[tnum].thread_num = tnum + 1;
               tinfo[tnum].argv_string = argv[optind + tnum];

               /* The pthread_create() call stores the thread ID into
                  corresponding element of tinfo[] */

               s = pthread_create(&tinfo[tnum].thread_id, &attr,
                                  &thread_start, &tinfo[tnum]);
               if (s != 0)
                   handle_error_en(s, "pthread_create");
           }

           /* Destroy the thread attributes object, since it is no
              longer needed */

           s = pthread_attr_destroy(&attr);
           if (s != 0)
               handle_error_en(s, "pthread_attr_destroy");

           /* Now join with each thread, and display its returned value */

           for (tnum = 0; tnum < num_threads; tnum++) {
               s = pthread_join(tinfo[tnum].thread_id, &res);
               if (s != 0)
                   handle_error_en(s, "pthread_join");

               printf("Joined with thread %d; returned value was %s\n",
                       tinfo[tnum].thread_num, (char *) res);
               free(res);      /* Free memory allocated by thread */
           }


           free(tinfo);
           exit(EXIT_SUCCESS);
       }

SEE ALSO
       getrlimit(2), pthread_attr_init(3), pthread_cancel(3), pthread_detach(3), pthread_equal(3), pthread_exit(3), pthread_getattr_np(3), pthread_join(3), pthread_self(3), pthreads(7)

COLOPHON
       This page is part of release 4.04 of the Linux man-pages project.  A description of the project, information about reporting bugs, and the latest version of this page, can be found at
       http://www.kernel.org/doc/man-pages/.

Linux                                                                                         2015-07-23                                                                             PTHREAD_CREATE(3)

· Demo1 单线程(创建线程pthread_t 、创建线程run方法pthread_create)

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

void* myfunc(void* args){
	printf("Hello World\n");
	return NULL;
}

int main(){
	pthread_t th;
	pthread_create(&th, NULL, myfunc, NULL);	//第一个参数是线程th的地址,第三个参数是指针函数的名字(是不是函数的指针不知道)
	pthread_join(th, NULL);	//等待线程th结束
	return 0;
}

编译运行:

[yg@ubuntu ~/arnold_test/20211013_pthread_test]5$ gcc pthread_test1.c -lpthread
[yg@ubuntu ~/arnold_test/20211013_pthread_test]6$ 
[yg@ubuntu ~/arnold_test/20211013_pthread_test]6$ 
[yg@ubuntu ~/arnold_test/20211013_pthread_test]6$ ./a.out 
Hello World

· Demo2 双线程(一个打印1,一个打印2)

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

void* myfunc1(void* args){
	for(int i = 1; i<1000000; i++){
			printf("%d", 1);
	}
	return NULL;
}

void* myfunc2(void* args){
	for(int i = 1; i<1000000; i++){
			printf("%d", 2);
	}
	return NULL;
}

int main(){
	pthread_t th1;
	pthread_t th2;
	
	pthread_create(&th1, NULL, myfunc1, NULL);	//第一个参数是线程th的地址,第三个参数是指针函数的名字(是不是函数的指针不知道)
	pthread_create(&th2, NULL, myfunc2, NULL);
	
	pthread_join(th1, NULL);	//等待线程th结束,注意这里第一个参数不用地址
	pthread_join(th2, NULL);
	return 0;
}

编译运行:

[yg@ubuntu ~/arnold_test/20211013_pthread_test]5$ gcc pthread_test1.c -lpthread
[yg@ubuntu ~/arnold_test/20211013_pthread_test]6$ 
[yg@ubuntu ~/arnold_test/20211013_pthread_test]6$ 
[yg@ubuntu ~/arnold_test/20211013_pthread_test]6$ ./a.out 

运行结果:可以看到,是交替运行的
在这里插入图片描述

pthread_create()函数第四个参数,用于传递第三个参数中函数的参数使用(参数你可以传递任何类型,到时转换成相应类型的指针即可)

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

void* myfunc(void* args){
	char* name = (char*)args;
	for(int i = 1; i<10000; i++){
			printf("%s:%d\t", name, i);
	}
	return NULL;
}

int main(){
	pthread_t th1;
	pthread_t th2;
	
	pthread_create(&th1, NULL, myfunc, "th1");	//第一个参数是线程th的地址,第三个参数是指针函数的名字(是不是函数的指针不知道)
	pthread_create(&th2, NULL, myfunc, "th2");
	
	pthread_join(th1, NULL);	//等待线程th结束,注意这里第一个参数不用地址
	pthread_join(th2, NULL);
	return 0;
}

编译运行:

[yg@ubuntu ~/arnold_test/20211013_pthread_test]5$ gcc pthread_test1.c -lpthread
[yg@ubuntu ~/arnold_test/20211013_pthread_test]6$ 
[yg@ubuntu ~/arnold_test/20211013_pthread_test]6$ 
[yg@ubuntu ~/arnold_test/20211013_pthread_test]6$ ./a.out 

运行结果:也可以看到有交替的
在这里插入图片描述

· Demo3 随机5000个数字,线程一加前2500个,线程2加后2500个,然后两个结果求和

我的垃圾实现- -

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

int arr[5000];	//创建一个包含大小为5000的整型数组
int s1;	//前2500个数字和,分配给线程1去加
int s2;	//后2500个数字和,分配给线程2去加

void* myfunc(void* args){
	int index = *(int*)args;
	//printf("%d\n", index);
	int count = 0;
	int sum = 0;
	while(count<2500){
		sum+=arr[index];
		index++;
		count++;
	}
	if(index-2500==0){
		s1+=sum;
	}else{
		s2+=sum;
	}
	return NULL;
}

int main(){
	//设置随机数种子(否则每次都一样结果)
	srand(time(0));
	
	int i;
	for(i=0;i<5000;i++){
		
		//给数组赋值
		arr[i]=rand()%50;	//rand()特别大,所以要取余
		//printf("%d\n", arr[i]);
		//arr[i]=i+1;	//测试1到5000和是12502500,没问题
		//printf("%d\n",arr[i]);
	}
	
	pthread_t th1;
	pthread_t th2;
	
	int a = 0;
	int b = 2500;
	
	pthread_create(&th1, NULL, myfunc, &a);	
	pthread_create(&th2, NULL, myfunc, &b);
	
	pthread_join(th1, NULL);	
	pthread_join(th2, NULL);
	
	printf("s1 = %d, s2 = %d\n", s1, s2);
	return 0;
}

编译运行结果:因为是对50取余,所以每个结果接近(0~49)* 2500 = 24.5 * 2500 = 61250

[yg@ubuntu ~/arnold_test/20211013_pthread_test]127$ gcc pthread_test1.c -lpthread
[yg@ubuntu ~/arnold_test/20211013_pthread_test]128$ ./a.out 
s1 = 60163, s2 = 61379
[yg@ubuntu ~/arnold_test/20211013_pthread_test]129$ ./a.out 
s1 = 62296, s2 = 60521
[yg@ubuntu ~/arnold_test/20211013_pthread_test]130$ ./a.out 
s1 = 61066, s2 = 61084
[yg@ubuntu ~/arnold_test/20211013_pthread_test]131$ ./a.out 
s1 = 61051, s2 = 61582
[yg@ubuntu ~/arnold_test/20211013_pthread_test]132$ 

老师的实现(通过传入结构体指针,获取返回值结果)

1、可以为每个线程单独写一个函数,这样虽然简答,但代码重复多

2、只写一个函数,在外部定义包含需要相加的数字下标起止点的结构体,同时在结构体中定义接收计算结果的变量,将结构体作为线程函数的第四个参数传入

代码实现:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

int arr[5000];	//创建一个包含大小为5000的整型数组
int s1;	//前2500个数字和,分配给线程1去加
int s2;	//后2500个数字和,分配给线程2去加

//typedef struct _MY_ARGS{
typedef struct{
	int start;
	int end;
	int result;
}MY_ARGS;

void* myfunc(void* args){
	MY_ARGS* p = (MY_ARGS*)args;
	int sum = 0;	//不要忘记赋初始值(!)
	int i;
	for(i= p->start; i < p->end; i++){
		sum+=arr[i];
		//printf("%d\n", arr[i]);
	}
	p->result = sum;
	return NULL;
}

int main(){
	//设置随机数种子(否则每次都一样结果)
	srand(time(0));
	
	int i;
	for(i=0;i<5000;i++){
		
		//给数组赋值
		arr[i]=rand()%50;	//rand()特别大,所以要取余
		//printf("%d\n", arr[i]);
		//arr[i]=i+1;	//测试1到5000和是12502500,没问题
		//printf("%d\n",arr[i]);
	}
	
	pthread_t th1;
	pthread_t th2;
	
	MY_ARGS args1 = {0, 2500, 0};
	MY_ARGS args2 = {2500, 5000, 0};
	
	pthread_create(&th1, NULL, myfunc, &args1);	
	pthread_create(&th2, NULL, myfunc, &args2);
	
	pthread_join(th1, NULL);	
	pthread_join(th2, NULL);
	
	printf("s1 = %d, s2 = %d\n", args1.result, args2.result);
	return 0;
}

编译运行结果:

[yg@ubuntu ~/arnold_test/20211013_pthread_test]182$ gcc pthread_test1.c -lpthread
[yg@ubuntu ~/arnold_test/20211013_pthread_test]183$ ./a.out 
s1 = 63337, s2 = 59578
[yg@ubuntu ~/arnold_test/20211013_pthread_test]184$ ./a.out 
s1 = 60652, s2 = 60818
[yg@ubuntu ~/arnold_test/20211013_pthread_test]185$ ./a.out 
s1 = 61265, s2 = 62009
[yg@ubuntu ~/arnold_test/20211013_pthread_test]186$ ./a.out 
s1 = 61265, s2 = 62009
[yg@ubuntu ~/arnold_test/20211013_pthread_test]187$ ./a.out 
s1 = 61912, s2 = 62479
[yg@ubuntu ~/arnold_test/20211013_pthread_test]188$ 

· Demo4 两个线程同时提取数组元素相加(数组元素为1~5000)

不加锁试试

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

int arr[5000];	//创建一个包含大小为5000的整型数组
int sum = 0;	//两个线程同时对sum进行操作

//typedef struct _MY_ARGS{
typedef struct{
	int start;
	int end;
}MY_ARGS;

void* myfunc(void* args){
	MY_ARGS* p = (MY_ARGS*)args;
	int i;
	for(i= p->start; i < p->end; i++){
		sum+=arr[i];
		//printf("%d\n", arr[i]);
	}
	return NULL;
}

int main(){
	//设置随机数种子(否则每次都一样结果)
	srand(time(0));
	
	int i;
	for(i=0;i<5000;i++){
		
		//给数组赋值
		//arr[i]=rand()%50;	//rand()特别大,所以要取余
		//printf("%d\n", arr[i]);
		arr[i]=i+1;	//测试1到5000和是12502500,没问题
		//printf("%d\n",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);
	
	printf("sum = %d", sum);
	return 0;
}

运行编译结果:

[yg@ubuntu ~/arnold_test/20211013_pthread_test]207$ gcc pthread_test1.c -lpthread
[yg@ubuntu ~/arnold_test/20211013_pthread_test]208$ ./a.out 
sum = 12502500[yg@ubuntu ~/arnold_test/20211013_pthread_test]209$ ./a.out 
sum = 12502500[yg@ubuntu ~/arnold_test/20211013_pthread_test]210$ ./a.out 
sum = 12502500[yg@ubuntu ~/arnold_test/20211013_pthread_test]211$ ./a.out 
sum = 12502500[yg@ubuntu ~/arnold_test/20211013_pthread_test]212$ ./a.out 
sum = 12502500[yg@ubuntu ~/arnold_test/20211013_pthread_test]213$ ./a.out 

可以看到,结果不全尽然是12502500,说明两个线程其中一个在把元素加到sum变量的同时,另一个线程也在把另一个元素往sum里面加,没排队按顺序加,导致结果不对

在这里插入图片描述

加mutex锁🔒(pthread_mutex_t lock;)

pthread_mutex_init() 锁初始化函数

在这里插入图片描述

代码示例(加 / 解线程锁:pthread_mutex_lock(&lock); pthread_mutex_unlock(&lock);)
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

int arr[5000];	//创建一个包含大小为5000的整型数组
int sum = 0;	//两个线程同时对sum进行操作

pthread_mutex_t lock;	//创建mutex锁🔒

//typedef struct _MY_ARGS{
typedef struct{
	int start;
	int end;
}MY_ARGS;

void* myfunc(void* args){
	MY_ARGS* p = (MY_ARGS*)args;
	int i;
	//int a;
	for(i= p->start; i < p->end; i++){
		pthread_mutex_lock(&lock);	//锁住代码
		
		//a=sum;
		sum+=arr[i];
		//printf("%d", sum-a-arr[i]);	//验证不加锁时会不按顺序乱加sum(如果出现不为0的数表示线程乱窜了)
		
		pthread_mutex_unlock(&lock);	//解锁代码
		//printf("%d\n", arr[i]);
	}
	return NULL;
}

int main(){
	//设置随机数种子(否则每次都一样结果)
	srand(time(0));
	
	int i;
	for(i=0;i<5000;i++){
		
		//给数组赋值
		//arr[i]=rand()%50;	//rand()特别大,所以要取余
		//printf("%d\n", arr[i]);
		arr[i]=i+1;	//测试1到5000和是12502500,没问题
		//printf("%d\n",arr[i]);
	}
	
	pthread_t th1;
	pthread_t th2;
	
	pthread_mutex_init(&lock, NULL);	//初始化mutex锁🔒
	
	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);
	
	printf("sum = %d", sum);
	return 0;
}

运行编译结果:

[yg@ubuntu ~/arnold_test/20211013_pthread_test]346$ gcc pthread_tedt1.c -lpthread
[yg@ubuntu ~/arnold_test/20211013_pthread_test]347$ ./a.out 
sum = 12502500[yg@ubuntu ~/arnold_test/20211013_pthread_test]348$ ./a.out 
sum = 12502500[yg@ubuntu ~/arnold_test/20211013_pthread_test]349$ ./a.out 
sum = 12502500[yg@ubuntu ~/arnold_test/20211013_pthread_test]350$ ./a.out 
sum = 12502500[yg@ubuntu ~/arnold_test/20211013_pthread_test]351$ ./a.out 
sum = 12502500[yg@ubuntu ~/arnold_test/20211013_pthread_test]352$ 

查看运行时间:看不出来,数字太小了(反正作者的意思就是,加锁解锁也需要消费时间!

[yg@ubuntu ~/arnold_test/20211013_pthread_test]352$ time ./a.out 
sum = 12502500
real	0m0.001s
user	0m0.001s
sys	0m0.000s

假共享(伪共享)(False Sharing):在多核cpu中,因为多线程不同核缓存的不一致,需要同步导致的时间延误?

https://www.bilibili.com/video/BV1kt411z7ND?p=4
在这里插入图片描述
这里面作者把两个线程计算结果分别存到同一个整型数组的连续两个位置,长度较短,于是RAM复制到Cache缓存的时候就会整体复制过去,,同步时就会产生较大的时间延误

怎么让它不整体复制呢,方法就是增加字符数组长度(增大到它不想整体复制为止),比如将第一个结果存到第一个int下,将第二个结果存到第101个int下,

20230217 使用votalile关键字的变量还会存在多线程伪共享问题吗?(伪共享只是导致时间延误、性能下降,不加votalile可能导致的是数据不同步,不是同一个问题!)

使用volatile关键字修饰的变量可以防止编译器在编译过程中进行某些优化,但是并不会对多线程伪共享问题进行保护。

多线程伪共享问题是由于多个线程同时访问同一缓存行中的变量,导致多个线程之间频繁地读写该缓存行,从而导致性能下降。volatile关键字只是保证了变量在多线程间的可见性,并不保证变量的内存布局,因此不能防止伪共享问题。

为了解决多线程伪共享问题,需要使用特定的技术,例如缓存行对齐、填充空间等。可以使用alignas关键字指定变量的对齐方式,从而确保不会跨越缓存行。也可以使用std::array等数据结构来进行填充。

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dontla

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值