编译
gcc pth.c -lpthread -o pth
代码
头文件
#include <pthread.h>
多个线程调用一个函数
#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;
}
#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;
}
多个进程多个函数
#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;
}
线程间通信
在线程间进行通信的方式有多种,下面是几种常见的方式:
共享内存:多个线程共享同一块内存区域,通过读写这块内存来进行通信。可以使用原子操作或者互斥锁等机制确保对共享数据的访问安全。
信号量:通过信号量来进行线程间的同步和互斥,实现线程之间的通信。一个线程可以通过sem_wait()函数等待信号量,而另一个线程可以通过sem_post()函数发送信号量,通知线程可以继续执行。
互斥锁:通过互斥锁来保护共享资源,确保在同一时间只有一个线程可以访问共享资源。一个线程可以通过使用pthread_mutex_lock()函数来获取互斥锁,而另一个线程可以通过pthread_mutex_unlock()函数释放互斥锁。
条件变量:通过条件变量来实现线程之间的协调和通信。一个线程可以通过pthread_cond_wait()函数等待条件变量满足,而另一个线程可以通过pthread_cond_signal()函数发送信号唤醒等待的线程。
管道:管道是一种半双工的通信方式,可以用于父子进程或者线程间的通信。一个线程可以通过写入管道,而另一个线程可以通过读取管道来进行通信。
这些是常见的线程间通信方式,选择合适的方式取决于具体的需求和场景。在使用这些通信方式时,需要注意线程安全和同步问题,避免出现竞态条件和错误的数据访问。
互斥锁
#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;
}
#include <stdio.h>
#include <pthread.h>
int global_variable = 0;
pthread_mutex_t mutex;
void* increment(void* arg) {
for (int i = 0; i < 10; i++) {
// 加锁,保护对全局变量的访问
pthread_mutex_lock(&mutex);
global_variable++;
printf("Increment: %d\n", global_variable);
// 解锁
pthread_mutex_unlock(&mutex);
}
pthread_exit(NULL);
}
void* print(void* arg) {
for (int i = 0; i < 10; i++) {
// 加锁,保护对全局变量的访问
pthread_mutex_lock(&mutex);
printf("Print: %d\n", global_variable);
// 解锁
pthread_mutex_unlock(&mutex);
}
pthread_exit(NULL);
}
int main() {
pthread_mutex_init(&mutex, NULL);
pthread_t thread_increment, thread_print;
pthread_create(&thread_increment, NULL, increment, NULL);
pthread_create(&thread_print, NULL, print, NULL);
pthread_join(thread_increment, NULL);
pthread_join(thread_print, NULL);
pthread_mutex_destroy(&mutex);
return 0;
}
在此示例中,我们使用pthread_mutex_t类型的互斥锁mutex来保护对全局变量global_variable的访问。在increment线程中,通过调用pthread_mutex_lock和pthread_mutex_unlock来分别加锁和解锁,对global_variable进行自增操作,并打印增加后的值。在print线程中,同样通过加锁和解锁,打印global_variable的值。
通过使用互斥锁来保护对共享资源的访问,可以确保在任何时刻只有一个线程可以访问该资源,避免了竞态条件和错误的数据访问。
原子变量
在Linux系统中,使用C语言给原子变量赋值通常涉及到<stdatomic.h>库或者GCC内置的原子操作。<stdatomic.h>库是C11标准的一部分,提供了跨平台的原子操作。如果你使用的是GCC编译器,你还可以利用GCC的内置函数来进行原子操作。
以下是一个使用<stdatomic.h>给原子变量counter_usb赋值的例子:
c
#include <stdio.h>
#include <stdatomic.h>
int main() {
// 定义一个原子整数变量counter_usb,初始值为0
atomic_int counter_usb = 0;
// 给counter_usb赋值
atomic_store(&counter_usb, 10);
// 输出counter_usb的值
printf("counter_usb: %d\n", atomic_load(&counter_usb));
return 0;
}
在这个例子中,atomic_int是一个原子整数类型,atomic_store函数用于给原子变量赋值,而atomic_load函数用于读取原子变量的值。
如果你使用的是GCC编译器,并希望利用GCC的内置函数,你可以这样做:
c
#include <stdio.h>
int main() {
// 定义一个原子整数变量counter_usb,初始值为0
typedef int atomic_int attribute((mode(QI)));
atomic_int counter_usb = 0;
// 给counter_usb赋值
__atomic_store_n(&counter_usb, 10, __ATOMIC_SEQ_CST);
// 输出counter_usb的值
printf("counter_usb: %d\n", __atomic_load_n(&counter_usb, __ATOMIC_SEQ_CST));
return 0;
}
在这个例子中,__atomic_store_n和__atomic_load_n是GCC提供的内置函数,用于原子地存储和加载整数值。__ATOMIC_SEQ_CST是一个内存顺序模型,表示顺序一致性(Sequentially Consistent),它是最强的内存顺序模型,确保了操作的全局顺序。
注意,使用原子操作通常是为了确保在多线程环境中对共享数据的访问是线程安全的。确保你的编译器和平台支持这些特性,并且你的代码在多线程环境中正确地使用了原子操作。