c语言多线程pthread的问题
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了c语言多线程pthread的问题相关的知识,希望对你有一定的参考价值。
给一个例子 并说说在vc中怎么配置
运行之前需要做一些配置:1.下载PTHREAD的WINDOWS开发包 pthreads-w32-2-4-0-release.exe(任何一个版本均可) http://sourceware.org/pthreads-win32/ ,解压到一个目录。2.找到include和lib文件夹,下面分别把它们添加到VC++6.0的头文件路径和静态链接库路径下面: a).Tools->Options,选择Directory页面,然后在Show directories for:中选择Include files(默认), 在Directories中添加include的路径。在Show directories for:中选择Library files, 在Directories中添加lib的路径。 b).Project->Settings,选择Link页面,然后将lib下的*.lib文件添加到Object/library Modules, 各lib文件以空格隔开。 c).将lib下的*.dll文件复制到工程目录下,即根目录。 如果不配置环境,将出现以下错误:Linking...工程名.obj : error LNK2001: unresolved external symbol __imp__pthread_join
工程名.obj : error LNK2001: unresolved external symbol __imp__pthread_create
Debug/工程名.exe : fatal error LNK1120: 2 unresolved externals
Error executing link.exe. 我自己以前编的一个例子,火车票窗口卖票的 如果不加锁就会出现-2,-1的问题 这里只是个事例 #include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <windows.h>int piao = 100; void* tprocess1(void* args)
while(piao>0)
Sleep(1);
piao--;
printf("窗口1----------------还剩%d张票\n",piao);
// Sleep(5);
return NULL;
void* tprocess2(void* args)
while(piao>0)
Sleep(1);
printf("窗口2----------------还剩%d张票\n",piao);
// Sleep(9);
return NULL;
void* tprocess3(void* args)
while(piao>0)
Sleep(1);
piao--;
printf("窗口3----------------还剩%d张票\n",piao);
// Sleep(10);
return NULL;
void* tprocess4(void* args)
while(piao>0)
Sleep(1);
piao--;
printf("窗口4----------------还剩%d张票\n",piao);
// Sleep(10);
return NULL;
int main()
pthread_t t1;
pthread_t t2;
pthread_t t3;
pthread_t t4;
pthread_create(&t1,NULL,tprocess1,NULL);
pthread_create(&t2,NULL,tprocess2,NULL);
pthread_create(&t3,NULL,tprocess3,NULL);
pthread_create(&t4,NULL,tprocess4,NULL);
pthread_join(t1,NULL);
pthread_join(t2,NULL);
pthread_join(t3,NULL);
pthread_join(t4,NULL);
参考技术A 建议您到文库找一下pthread库里面的一些函数的说明,不是三言两语就能说完了。您要先确定自己要实现的目的,不要为了用线程而用线程。
C 语言编程 — pthread 线程操作
目录
文章目录
用户线程
User Thread 是由 User Process 负责创建、调度和销毁的线程,运行在 User Space 中,Kernel 对其不感知也不管理。
一个 User Process 可以拥有多个 User Threads,且至少拥有一个 Main Thread(初始线程)。Main Thread 是 User Process 的第一个 Thread(TID 为 1),并且本质也是一个 User Thread,用于从 main() 开始执行 User Process 自身的程序代码。
当开发者通过 pthread 库创建了多个 User Threads 后,这些 User Threads 就会共享同一个 User Process 的资源。对于 User Thread 而言,它只具备在 CPU 上执行所必需的资源,例如:PC(程序计数器)、Registers(寄存器)和 Stack(栈)。所以 User Thread 非常的轻量,适用于高并发的场景。
多线程的切换
由于 User Thread 不被 Kernel 感知,不参与直接的 CPU 调度,但 User Thread 同样存在在同一个 CPU 上的切换行为。即:同一个 User Process 中的多个User Threads 只会在 User Process 所调度的 CPU 上进行线程切换。
而 User Thread 的切换完全是 User Process 决定的,也就是由开发者实现的代码逻辑来控制的,所以通常称之为 “协作式调度”,即:多个 User Thread 之间自行协商让出 CPU。所以,User Thread 的协作式调度不存在 CPU 在用户模式和内核模式之间的切换消耗。
pthread 线程库
在 pthread 中,使用 TCB(Thread Control Block,线程控制块)来存储 User Thread 的所有信息,TCB 的体量会比 PCB 小很多。对应的 pthread 结构体如下:
// pthread/pthread_impl.h
struct pthread
struct pthread *self; // 指向自身的指针
struct __pthread_internal_list *thread_list; // 线程列表,指向线程列表的指针,用于实现线程池;
void *(*start_routine)(void*); // 线程的入口函数,由 pthread_create() 函数传入;
void *arg; // 线程的入口函数参数,由 pthread_create() 函数传入;
void *result; // 线程的返回值,由线程的入口函数返回;
pthread_attr_t *attr; // 线程的属性,包括栈保护区大小、调度策略等,由 pthread_create() 函数传入;
pid_t tid; // 线程的唯一标识符,由 Kernel 分配;
struct timespec *waiters; // 等待的时间戳
size_t guardsize; // 栈保护区大小
int sched_policy; // 调度策略
struct sched_param sched_params; // 调度参数
void *specific_1stblock; // 线程私有数据的第一个块
struct __pthread_internal_slist __cleanup_stack; // 清理函数栈
struct __pthread_mutex_s *mutex_list; // 线程持有的互斥锁列表
struct __pthread_cond_s *cond_list; // 线程等待的条件变量列表
unsigned int detach_state:2; // 线程分离状态,包括分离和未分离两种;
unsigned int sched_priority:30; // 线程的调度优先级
unsigned int errno_val; // 线程的错误码
;
pthread 库,围绕 struct pthread 提供了一系列的接口,用于完成 User Thread 管理。
线程的创建和销毁
pthread_create()
函数作用:用于创建一条新的线程,并指定线程的入口函数和参数。
函数原型:
- thread 参数:是一个 pthread_t 类型指针,用于存储 TID。TID 由 Kernel 随机分配。
- attr 参数:是一个 pthread_attr_t 类型指针,用于指定线程的属性,包括指定栈保护区大小、调度策略、优先级等,通常为 NULL。
- start_routine 参数:线程入口函数,是一个 void* 类型函数指针(或直接使用函数名)。线程入口函数必须是一个 static 静态函数或全局函数,因为 pthread 会把线程入口函数的返回值传递到 pthread_join() 中,所以需要能够找到它。
- arg 参数:线程参数,是一个 void* 类型参数。
- 函数返回值:
- 成功:返回 0;
- 失败:返回 -1;
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
Main/Parent Thread 调用 pthread_create() 后,pthread 库就会为 Child Thread 分配 TCB、PC(程序计数器)、Registers(寄存器)和 Stack(栈)等资源。随后 pthread 库会初始化 TCB,并将其加入到 Thread Pool 中等待执行。直到 User Thread 被调度到 CPU 时,开始执行线程入口函数。
pthread_join()
函数作用:如果 Parent Thread 希望获得 Child Thread 的执行结果,可以通过 pthread_join() 来实现,用于等待指定的 Child Thread 退出,并获得线程入口函数的返回值。此时 Parent Thread 会被阻塞。
函数原型:
- thread 参数:指定等待的 TID。
- retval:是一个指向指针的指针类型,用于存储 Child 的退出码。
int pthread_join(pthread_t thread, void **retval);
Parent Thread 调用 pthread_join() 后,就会开始 Sleeping 等待 Child Thread 的执行结果。
pthread_exit()
函数作用:User Thread 调用后立即终止自己,并返回一个退出码。
函数原型:
- retval:是一个指针类型,用于存储退出码。如果不需要返回值,则设置为 NULL。
void pthread_exit(void *retval);
pthread_exit() 会自动释放当前 User Thread 所占用的所有资源。但需要注意的是,如果 Main Thread 调用了 pthread_exit(),那么就会终止整个 User Thread,因此需要额外小心。
pthread_detach()
函数作用:用于将线程设置为可分离状态。可分离状态的线程在退出时,系统可以自动回收线程资源,而无需通过调用 pthread_join 函数来进行回收。
函数原型:
- thread 参数:指定 TID。
int pthread_detach(pthread_t thread);
多线程安全与多线程同步
多线程安全(Multi-Thread Safe),就是在多线程环境中,多个线程在同一时刻对同一份共享数据(Shared Resource,e.g. 寄存器、内存空间、全局变量、静态变量 etc.)进行写操作(读操作不会涉及线程安全的问题)时,不会出现数据不一致。
为了确保在多线程安全,就要确保数据的一致性,即:线程安全检查。多线程之间通过需要进行同步通信,以此来保证共享数据的一致性。
pthread 库提供了保证线程安全的方式:
- 互斥锁(Mutex):是一种线程安全机制,为共享数据加上一把锁,拥有锁的线程,才可以访问共享数据。以此保护共享数据不被多个线程同时访问。
- 条件变量(Condition Variable):是一种线程同步机制,用于判断线程是否满足了特定的竞争条件(Race Condition)。只有满足条件的线程,才可以获得互斥锁,以此来避免死锁的情况。
需要注意的是,线程安全检查的实现会带来一定的系统开销。
互斥锁(Mutex)
pthread_mutex_init()
函数作用:用于初始化一个互斥锁实体。
函数原型:
- mutex 参数:pthread_mutex_t 类型指针,用于指定要初始化的互斥锁。
- attr 参数:pthread_mutexattr_t 类型指针,用于指定互斥锁的属性,例如:递归锁、非递归锁等,通常为 NULL。
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
pthread_mutex_lock()
函数作用:User Thread 用于获取互斥锁。如果互斥锁已被 Other User Thread 获得,则当前 User Thread 会阻塞。
函数原型:
- mutex 参数:pthread_mutex_t 类型指针,用于指定要获取的互斥锁。
int pthread_mutex_lock(pthread_mutex_t *mutex);
pthread_mutex_unlock()
函数作用:User Thread 用于释放互斥锁,互斥锁重回可用状态。如果当前 User Thread 并没有锁,则该函数可能会产生未定义行为。
函数原型:
- mutex 参数:pthread_mutex_t 类型指针,用于指定要释放的互斥锁。
int pthread_mutex_unlock(pthread_mutex_t *mutex);
条件变量(Condition Variable)
互斥锁和条件变量都是多线程同步的工具,但是它们的作用不同:
- 互斥:互斥锁可以保护共享资源的访问,防止多个线程同时修改共享资源,但是它无法告知其他线程何时可以安全地访问共享资源,有可能导致死锁的发生。
举例来说,存在全局变量 n(共享数据)被多线程访问。当 TA 获得锁后,在临界区中访问 n,且只有当 n > 0 时,才会释放锁。这意味着当 n == 0 时,TA 将永远不会释放锁,从而造成死锁。
那么解决死锁的方法,就是设定一个条件:只有当 n > 时,TA 才可以获得锁。而这个条件,就是多线程之间需要同步的信息。即:在多线程环境中,当一个线程需要等待某个条件成立时,才可以获得锁,那么应该使用条件变量来实现。
- 同步:pthread 条件变量提供了一种线程同步机制,当特定的事件发生时,它可以唤醒一个或多个在等待事件的线程,从而实现线程间的同步和协调。条件变量通常与互斥锁一起使用,以避免竞态条件和死锁的发生。
pthread_cond_init()
函数作用:用于初始化一个条件变量实体。
函数原型:
- cond 参数:pthread_cond_t 类型指针,用于指定要初始化的条件变量。
- attr 参数:pthread_condattr_t 类型指针,用于指定条件变量的属性,通常为 NULL。
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
pthread_cond_wait()
函数作用:线程用于等待某个条件变量满足。
- 当 T1 线程调用 pthread_cond_wait() 时,会自动地释放掉互斥锁,并阻塞线程,开始等待。
- 直到另一个 T2 线程调用了 pthread_cond_signal() 或 pthread_cond_broadcast(),以此来通知 T1 条件变量满足了。
- 然后 T1 pthread_cond_wait() 重新获取指定的互斥锁并返回。
函数原型:
- cond 参数:pthread_cond_t 类型指针,用于指定要等待的条件变量。
- mutex 参数:pthread_mutex_t 类型指针,用于指定要关联的互斥锁。在等待期间,线程将释放该互斥锁。
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
pthread_cond_signal()
函数作用:用于向等待条件变量的线程发送信号,唤醒其中的一个线程。
函数原型:
- cond 参数:pthread_cond_t 类型指针,用于指定要发送信号的条件变量。
int pthread_cond_signal(pthread_cond_t *cond);
pthread_cond_broadcast()
函数作用:用于向等待条件变量的所有线程发送信号,唤醒所有等待的线程。
函数原型:
- cond 参数:pthread_cond_t 类型指针,用于指定要发送信号的条件变量。
int pthread_cond_broadcast(pthread_cond_t *cond);
互斥锁和条件变量配合使用
当一个线程需要某个条件成立后才可以访问共享数据时。需要先锁定一个互斥锁,然后检查条件变量,如果条件不满足,则需要挂起并等待。
#include <pthread.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int data = 0; // 共享数据
void *producer(void *arg)
for (int i = 0; i < 10; i++)
pthread_mutex_lock(&mutex); // 加锁
data++; // 修改共享数据
pthread_cond_signal(&cond); // 发送信号
pthread_mutex_unlock(&mutex); // 解锁
sleep(1);
pthread_exit(NULL);
void *consumer(void *arg)
while (1)
pthread_mutex_lock(&mutex); // 加锁
while (data == 0) // 如果没有数据就等待信号
pthread_cond_wait(&cond, &mutex);
printf("data = %d\\n", data); // 打印共享数据
data--; // 修改共享数据
pthread_mutex_unlock(&mutex); // 解锁
sleep(1);
pthread_exit(NULL);
int main()
pthread_t tid1, tid2;
pthread_create(&tid1, NULL, producer, NULL);
pthread_create(&tid2, NULL, consumer, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
return 0;
线程非安全标准库函数
C 语言提供的大部分标准库函数都是线程安全的,但是也有几个常用函数是线程不安全的,也称为不可重入函数,原因是使用了某些全局或者静态变量。
我们知道,全局变量和静态变量分别对应内存中的全局变量区和静态存储区,这些区域都是可以跨线程访问的。在多线程环境中,这些数据如果在没有加锁的情况下并行读写,就会造成 Segmentfault / CoreDump 之类的问题。
- 不可重入函数汇总:
以上是关于c语言多线程pthread的问题的主要内容,如果未能解决你的问题,请参考以下文章
C语言多线程教程(pthread)(线程创建pthread_t,指定线程run方法pthread_create,加mutex锁,解锁,伪共享 false sharing假共享)
linux C语言 多线程竞争(加锁解锁 pthread_mutex_tpthread_mutex_lock()pthread_mutex_unlock() 可解决)