有一个专门做演讲的网站,深圳网站建设力荐上榜网络,wordpress排行榜主题,做网站要不要35类商标多线程线程线程的优点C语言多线程创建线程终止线程连接和分离线程开启一个线程最基本的多线程实现开启两个线程多线程进行协同运算无参数传递的线程并发编程实例简单参数传递的线程并发编程实例结构体参数传递的线程并发编程实例线程的连接编程实例信号量同步进行写入互斥信号量…
多线程线程线程的优点C语言多线程创建线程终止线程连接和分离线程开启一个线程最基本的多线程实现开启两个线程多线程进行协同运算无参数传递的线程并发编程实例简单参数传递的线程并发编程实例结构体参数传递的线程并发编程实例线程的连接编程实例信号量同步进行写入互斥信号量实现对临界资源操作并发程序引起的共享内存的问题在串口助手编程中-k命令下需要实现等待接收message的同时可以发送键入message。但是键入message使用的fgets()函数如果得不到键入就会一直等待无法继续接收message考虑采用多线程实现有键入则发送否则一直等待接收message。线程
线程在Unix系统下通常被称为轻量级的进程线程虽然不是进程但却可以看作是Unix进程的表亲同一进程中的多条线程将共享该进程中的全部系统资源如虚拟地址空间文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈(call stack)自己的寄存器环境register context)自己的线程本地存储(thread-local storage)。 一个进程可以有很多线程每条线程并行执行不同的任务。
线程的优点
线程可以提高应用程序在多核环境下处理诸如文件I/O或者socket I/O等会产生堵塞的情况的表现性能。在Unix系统中一个进程包含很多东西包括可执行程序以及一大堆的诸如文件描述符地址空间等资源。在很多情况下完成相关任务的不同代码间需要交换数据。如果采用多进程的方式那么通信就需要在用户空间和内核空间进行频繁的切换开销很大。但是如果使用多线程的方式因为可以使用共享的全局变量所以线程间的通信数据交换变得非常高效。
C语言多线程
多线程是多任务处理的一种特殊形式多任务处理允许让电脑同时运行两个或两个以上的程序。一般情况下两种类型的多任务处理基于进程和基于线程。
基于进程的多任务处理是程序的并发执行。 基于线程的多任务处理是同一程序的片段的并发执行。 多线程程序包含可以同时运行的两个或多个部分。这样的程序中的每个部分称为一个线程每个线程定义了一个单独的执行路径。
本教程假设您使用的是 Linux 操作系统我们要使用 POSIX 编写多线程 C 程序。POSIX Threads 或 Pthreads 提供的 API 可在多种类 Unix POSIX 系统上可用比如 FreeBSD、NetBSD、GNU/Linux、Mac OS X 和 Solaris。
创建线程
首先c语言的多线程并发需要用到 pthread.h 库。
#include pthread.hFor example - pthread_t thrd1; - pthread_attr_t attr; - void thread_function(void argument); - char *some_argument;pthread_create(thrd1, NULL, (void *)thread_function, (void *) some_argument)
线程创建函数包含四个变量分别为 1. 一个线程变量名被创建线程的标识 2. 线程的属性指针缺省为NULL即可 3. 被创建线程的程序代码 4. 程序代码的参数
创建一个POSIX 线程
pthread_create (thread, attr, start_routine, arg)pthread_create 创建一个新的线程并让它可执行。
参数描述thread指向线程标识符指针。attr一个不透明的属性对象可以被用来设置线程属性。您可以指定线程属性对象也可以使用默认值 NULL。start_routine线程运行函数起始地址一旦线程被创建就会执行。arg运行函数的参数。它必须通过把引用作为指针强制转换为 void 类型进行传递。如果没有传递参数则使用 NULL。
创建线程成功时函数返回 0若返回值不为 0 则说明创建线程失败。
终止线程
pthread_exit(void *retval); //retval用于存放线程结束的退出状态终止一个 POSIX 线程
#include pthread.h
pthread_exit (status)pthread_exit 用于显式地退出一个线程。通常情况下pthread_exit() 函数是在线程完成工作后无需继续存在时被调用。
如果 main() 是在它所创建的线程之前结束并通过 pthread_exit() 退出那么其他线程将继续执行。否则它们将在 main() 结束时自动被终止。
连接和分离线程
pthread_create调用成功以后新线程和老线程谁先执行谁后执行用户是不知道的这一块取决与操作系统对线程的调度如果我们需要等待指定线程结束需要使用pthread_join函数这个函数实际上类似与多进程编程中的waitpid。 举个例子以下假设 A 线程调用 pthread_join 试图去操作B线程该函数将A线程阻塞直到B线程退出当B线程退出以后A线程会收集B线程的返回码。 该函数包含两个参数
pthread_t th //th是要等待结束的线程的标识
void **thread_return //指针thread_return指向的位置存放的是终止线程的返回状态。
pthread_join (threadid, status)
pthread_detach (threadid)pthread_join() 子程序阻碍调用程序直到指定的 threadid 线程终止为止。当创建一个线程时它的某个属性会定义它是否是可连接的joinable或可分离的detached。只有创建时定义为可连接的线程才可以被连接。如果线程创建时被定义为可分离的则它永远也不能被连。pthread_join() 函数来等待线程的完成。
开启一个线程
最基本的多线程实现
#includestdio.h
#includestdlib.h
#includepthread.hvoid* func(void *args){printf(hello\n);return NULL;
}int main(){pthread_t th;pthread_create(th, NULL, func, NULL);pthread_join(th, NULL);return 0;
}主要分为三步
声明一个线程变量th类型为pthread_t使用pthread_create函数创建第一个参数是线程变量的地址第三个参数是线程执行的函数pthread_join函数等待 注意pthread库不是Linux系统默认的库连接时需要使用库libpthread.a, 在使用pthread_create创建线程时在编译中要加-lpthread参数:gcc xxx.c -lpthread -o xxx.o ./xxx 开启两个线程
#includestdio.h
#includestdlib.h
#includepthread.hvoid* func(void *args){int i;for(i1; i500; i){printf(%d\n, i);}return NULL;
}int main(){pthread_t th1;pthread_t th2;pthread_create(th1, NULL, func, NULL);pthread_create(th2, NULL, func, NULL);pthread_join(th1, NULL);pthread_join(th2, NULL);return 0;
}两个线程同时执行func函数错序打印1~499 如果用到pthread_create函数的第4个参数这个参数的传入会反应到func中的形参中去。
void* func(void *args){int i;char *name (char*)args;for(i1; i500; i){printf(%s:%d\n, name, i);}return NULL;
}int main(){pthread_t th1;pthread_t th2;pthread_create(th1, NULL, func, th1);pthread_create(th2, NULL, func, th2);pthread_join(th1, NULL);pthread_join(th2, NULL);return 0;
}输出的结果我们可以清晰地看出th1和th2的线程标记和交错运行。
多线程进行协同运算
创建一个数组其中有5000个元素我们想用两个线程来共同计算这5000个元素的加法和。
int arr[5000];
int s1 0;
int s2 0;
void *func1(void *args){int i;char *name (char *)args;for(i 1; i 2500; i){s1 arr[i];}return NULL;
}
void *func1(void *args){int i;char *name (char *)args;for(i 2500; i 5000; i){s2 arr[i];}return NULL;
}从两个线程的函数可以看出一个线程计算前2500个值的加法和另一个线程计算后2500个值的加法和。
int main(){int i;for(i 0; i 5000; i){arr[i] rand() % 50;}pthread_ th1;pthread_t th2;pthread_create(th1, NULL, func1, NULL);pthread_create(th2, NULL, func2, NULL);pthread_join(th1, NULL);pthread_join(th2, NULL);printf(s1 %d, s2 %d, s1s2 %d\n, s1, s2, s1s2);return 0;
}main函数中在pthread_join函数等待的th1和th2都结束后输出对应的值。
代码里th1和th2的执行函数中有大量的相似代码所以我们最后用一个函数来复用。不难想到需要通过传参的方式来实现代码复用。这里我们定义一个结构体结构体中有循环的起始标记first终止标记last区间内加法和result。
typedef struct{int first;int last;int result;
}MY_ARGS;
int arr[5000];
void *func(void *args){int i;int s 0;//参数强制类型转换MY_ARGS *my_args (MY_ARGS *)args;for(i my_args-first; i my_args-last; i){s arr[i];}my_args-result s;return NULL;
}把func1和fun2整合到了func中去。而在main函数中我们创建线程的时候传入的参数正是结构体指针
int main(){int i;for(i 0; i 5000; i){arr[i] rand() % 50;}pthread_t th1;pthread_t th2;MY_ARGS args1 {0, 2500, 0};MY_ARGS args2 {2500, 5000, 0};pthread_create(th1, NULL, func, args1);pthread_create(th2, NULL, func, args2);pthread_join(th1, NULL);pthread_join(th2, NULL);printf(s1 %d, s2 %d, s1s2 %d\n, args1.result, args2.result, args1.resultargs2.result);return 0;
}这样在func函数中我们就可以对传入的结构体参数中的元素进行利用了将计算所得传到结构体的result中去。这样我们输出加法和就可以得到跟上面一样的结果但是代码会更整洁漂亮
无参数传递的线程并发编程实例
// 基于线程的并发编程
#include stdio.h
#include pthread.h
#define NUM_Threads 5// 线程的运行函数
void *PrintHello(void *arg)
{printf(Hello,World of Thread in C!\n);return 0;
}int main()
{int i;int ret;// 定义线程的id变量多个变量使用数组pthread_t tids[NUM_Threads];for (i0; iNUM_Threads; i){// 参数依次是 创建的线程id线程参数调用的函数传入的函数参数ret pthread_create(tids[i], NULL, PrintHello, NULL);if (ret ! 0){printf(pthread_create error: error_code \n);}}// 等各个线程推出后进程才结束pthread_exit(NULL);return 0;
}/** 在CLion(Ubuntu)中输出结果为
Hello,World of Thread in C!
Hello,World of Thread in C!
Hello,World of Thread in C!
Hello,World of Thread in C!
Hello,World of Thread in C!* */简单参数传递的线程并发编程实例
// 基于线程的并发编程,向线程传递参数1
// Test_2_createThread
#include stdio.h
#include pthread.h
#include stdlib.h
#define NUM_Threads 5// 线程的运行函数
void *PrintHelloId(void *threadid)
{// 对传入的参数进行强制类型转换由无类型指针变为整形指针然后再读取int tid *((int *)threadid);printf(Hello,World Thread %d\n,tid);return 0;
}int main()
{pthread_t pthreads[NUM_Threads];int i, rc;// 用数组存储i的数值int indexes[NUM_Threads];for (i0; iNUM_Threads; i){printf(main() : 创建线程 %d \n,i);indexes[i] i; // 保存i的数值// 在indexes传入参数的时候必须转换为无类型指针rc pthread_create(pthreads[i], NULL, PrintHelloId, (void *)indexes[i]);if (0 ! rc){printf(Error: 无法创建线程\n);exit(-1);}}pthread_exit(NULL);return 0;
}/** 在CLionUbuntu中输出结果是
main() : 创建线程 0
main() : 创建线程 1
Hello,World Thread 0
main() : 创建线程 2
Hello,World Thread 1
main() : 创建线程 3
Hello,World Thread 2
main() : 创建线程 4
Hello,World Thread 3
Hello,World Thread 4* */结构体参数传递的线程并发编程实例
// 基于线程的并发编程,向线程传递参数2(传递结构体)
// Test_3_createThread
#include stdio.h
#include pthread.h
#include stdlib.h
#define NUM_Threads 5typedef struct thread_data{int threadid;char message;
}THDATA,*PTHDATA;void * PrintHello(void * pthreadid)
{PTHDATA tid (PTHDATA)pthreadid;printf(This is Pthread : %d ;info : %c \n,tid-threadid, tid-message);return 0;
}int main(void)
{pthread_t Pthread[NUM_Threads];THDATA index[NUM_Threads];int i, ret;for (i 0; i NUM_Threads; i){printf(main() : 创建线程 %d \n,i);index[i].threadid i;index[i].message Ai%10;ret pthread_create(Pthread[i], NULL, PrintHello, (void *)index[i]);if (0 ! ret){printf(Error: 创建线程失败\n);exit(-1);}}pthread_exit(NULL);return 0;
}/** 在CLionUbuntu中输出结果是
main() : 创建线程 0
main() : 创建线程 1
This is Pthread : 0 ;info : A
main() : 创建线程 2
main() : 创建线程 3
This is Pthread : 2 ;info : C
main() : 创建线程 4
This is Pthread : 3 ;info : D
This is Pthread : 4 ;info : E
This is Pthread : 1 ;info : B* */线程的连接编程实例
// 基于线程的并发编程,连接或分离线程
// Test_4_createThread
#include stdio.h
#include pthread.h
#include stdlib.h#define NUM_Pthread 5void *PrintHello(void * pthreadid)
{int tid *((int *)pthreadid);printf(Sleeping in thread %d ,...exiting \n,tid);return 0;
}int main(void)
{int i, ret;pthread_t Pthread[NUM_Pthread];pthread_attr_t attr; // 定义线程属性void * status;int index[NUM_Pthread];// 初始化并设置线程为可连接pthread_attr_init(attr);pthread_attr_setdetachstate(attr, PTHREAD_CREATE_JOINABLE);for (i0; iNUM_Pthread; i){printf(main() : 创建线程 %d \n,i);index[i] i;ret pthread_create(Pthread[i], NULL, PrintHello, (void *)index[i]);}// 删除属性并等待其他线程pthread_attr_destroy(attr);for (i0; iNUM_Pthread; i){ret pthread_join(Pthread[i], status);if (0 ! ret){printf(Error: unable to join,%d\n,ret);exit(-1);}printf(main(): complete thread id : %d,i);printf( exiting with status : %p\n,status);}printf(main() : program exiting.\n);pthread_exit(NULL);return 0;
}信号量同步进行写入
// 用信号量进行同步
#include stdio.h
#include pthread.h
#include stdlib.h
#include string.h
#include unistd.h
#include semaphore.h#define Len 100 // 设置输入内容长度sem_t bin_sem;
char work_area[Len]; // 存放输入内容void *Thread_func(void *arg)
{// 等待信号量有大于0的值然后退出sem_wait(bin_sem);while (0 ! strncmp(end, work_area, 3)){printf(Input %ld characters\n, strlen(work_area)-1);}return 0;
}int main(void)
{int res; // 存放命令的返回值pthread_t Pthread; // 创建线程void *thread_result; // 存放线程处理结果// 初始化信号量并设置初始值为0res sem_init(bin_sem, 0, 0);if (0 ! res){perror(Semaphore initialization failes);exit(EXIT_FAILURE);}// 创建新线程 0res pthread_create(Pthread, NULL, Thread_func, NULL);if (0 ! res){perror(Thread creation failed);exit(EXIT_FAILURE);}printf(Enter end to finish\n);// 当工作区内不是以end开头的字符串则继续输入while (0 ! strncmp(end, work_area, 3)){// 以标准输入获取输入到工作区内fgets(work_area, Len, stdin);sem_post(bin_sem); // 信号量1}printf(\n Waiting for thread to finish...\n);// 等待线程结束res pthread_join(Pthread, thread_result);if (0 ! res){perror(Thread join failed);exit(EXIT_FAILURE);}printf(Thread joined\n);sem_destroy(bin_sem); // 销毁信号量exit(EXIT_SUCCESS);return 0;
}互斥信号量实现对临界资源操作
// 用互斥信号量进行同步
#include stdio.h
#include pthread.h
#include stdlib.h
#include string.h#define Len 3 // 自增计算次数
#define NUM_Pthread 5 // 设置线程的长度int count 1; // 在数据段共享资源多个进程抢占临界资源
// 对于临界资源应该添加互斥锁
pthread_mutex_t mutex PTHREAD_MUTEX_INITIALIZER;void *Thread_func(void *threadid)
{int tid *((int *)threadid);int i, val;printf(Pthread ID : %d \n,tid);for (i0; iNUM_Pthread; i){pthread_mutex_lock(mutex);val count;printf(val %d \n,val);count val;pthread_mutex_unlock(mutex);}return 0;
}int main(void)
{int res; // 存放命令的返回值int i;pthread_t Pthread[NUM_Pthread]; // 创建线程int index[NUM_Pthread];for (i0; iNUM_Pthread; i){index[i] i;// 创建线程res pthread_create(Pthread[i], NULL, Thread_func, (void *)index[i]);if (0 ! res){printf(Error: 创建线程失败\n);exit(-1);}}for (i0; iNUM_Pthread; i){// 汇合线程pthread_join(Pthread[i], NULL);}printf(count %d\n,count);pthread_exit(NULL);return 0;
}// 在运行此程序无互斥锁时我们不仅得到错误的答案而且每次得到的答案都不相同
// 分析
// 当多个对等线程在一个处理器上并发运行时机器指令以某种顺序完成每个并发执行定义了线程中指令的某种顺序并发程序引起的共享内存的问题
有两个进程两个进程共享全局变量s。两个进程都执行一个计数功能的函数直观地看过去th1运行时s要执行10000次th2运行时s也要执行10000次似乎计算得到的最后s应该是20000。但实际上是这样的吗
#include stdio.h
#include pthread.h
#include stdlib.h
int s 0;
void *func(void*args){int i;for(i 0; i 10000; i){s;}return NULL;
}
int main(){pthread_t th1;pthread_t th2;pthread_create(th1, NULL, func, NULL);pthread_create(th2, NULL, func, NULL);pthread_join(th1, NULL);pthread_join(th2, NULL);printf(%s %d\n, s);return 0;
}编译运行后发现输出并不是20000而是12657。
原因当我们执行s底层发生的事件其实是内存中读取s→将s1→将s写入到内存。这不是一个原子化操作当两个线程交错运行的时候很容易发生结果的丢失。因此最后的结果肯定是要小于20000的。这种情况有种专有名词叫race condition。
为了解决这个问题我们可以加锁。
#include pthread.h
int s 0;
pthread_mutex_t lock; //锁的声明
void *func(void *args){int i;for(i 0; i 10000; i){ //给临界区代码加锁实现原子化操作pthread_mutex_lock(lock);s;pthread_mutex_unlock(lock);}return NULL;
}
int main(){pthread_t th1;pthread_t th2;//锁初始化pthread_mutex_init(lock, NULL);pthread_create(th1, NULL, func, NULL);pthread_createe(th2, NULL, func, NULL);pthread_join(th1, NULL);pthread_join(th2, NULL);printf(s %d\n, s);return 0;
}改进后的代码如下学过操作系统会很好理解无非就是为了保证共享内存区(临界区)的原子化操作我们可以在进这段代码之前加锁(pthread_mutex_lock)意味着其他线程看到这段内存被其他人占有的时候就不去抢占等这段内存被解锁(pthread_mutex_unlock)之后它才有读写这段临界区的权利。
但其实这种方式的执行速度并不快比如这段代码里每个线程都要进行10000次加解锁的操作它能解决内存读写冲突的问题但是却牺牲了效率。